From c38a399405dc1f26c472e4ffd0544b8adf3adf73 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 21 Dec 2021 13:50:24 +0800 Subject: [PATCH 01/78] chore(package): upgrade coc.nvim --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index b09a907..f1e7fe1 100644 --- a/package.json +++ b/package.json @@ -685,7 +685,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^10.12.0", - "coc.nvim": "^0.0.81-next.6", + "coc.nvim": "^0.0.81-next.8", "esbuild": "^0.8.29", "semver": "^7.3.2", "vscode-languageserver-protocol": "^3.16.0", diff --git a/yarn.lock b/yarn.lock index a598ef9..ba5ce3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.44.tgz#3945e6b702cb6403f22b779c8ea9e5c3f44ead40" integrity sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw== -coc.nvim@^0.0.81-next.6: - version "0.0.81-next.6" - resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.6.tgz#c3ee7079a66702ebb3b06d4c2bf333d9306ec561" - integrity sha512-VT+DhygyTIzu9IRrwCUljMzfNfh8TeXqqrvFsBE0E8cUwERgCAIvRbBMEDfqaaI+XFgyuwNRwbX5kEvfjG/u3g== +coc.nvim@^0.0.81-next.8: + version "0.0.81-next.8" + resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.8.tgz#7c0ec46cfab29d41ebdb9e3e9cabf1cb5cc2b920" + integrity sha512-1dk2j571Gxk4lXw2GVdGfOJeZAhfZ5HgmtA/p4NW5gorNVnCcC6qe2sp0LiecgiWfah3o+BsOTahr+Vjwj6GYA== esbuild@^0.8.29: version "0.8.29" From e4763a382073cc33fdb42f326fd70c37d0266c2e Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Tue, 21 Dec 2021 13:52:08 +0800 Subject: [PATCH 02/78] feat: add jsxAttributeCompletionStyle settings (#319) * docs: update yarn configuration (#311) * feat: add jsxAttributeCompletionStyle settings https://github.com/microsoft/vscode/pull/133920 Co-authored-by: KY64 <31939494+KY64@users.noreply.github.com> --- Readme.md | 7 ++-- package.json | 32 +++++++++++++++++++ .../features/fileConfigurationManager.ts | 10 ++++++ 3 files changed, 46 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index a29b158..00d1483 100644 --- a/Readme.md +++ b/Readme.md @@ -48,14 +48,15 @@ In your vim/neovim, run command: For yarn2 ( >= v2.0.0-rc.36) user want to use local typescript module: -- Run command `yarn dlx @yarnpkg/pnpify --sdk vim`, which will generate +- Run command `yarn dlx @yarnpkg/sdks vim`, which will generate `.vim/coc-settings.json`, with content: ```json { - "tsserver.tsdk": ".yarn/sdks/typescript/lib", "eslint.packageManager": "yarn", - "eslint.nodePath": ".yarn/sdks" + "eslint.nodePath": ".yarn/sdks", + "workspace.workspaceFolderCheckCwd": false, + "tsserver.tsdk": ".yarn/sdks/typescript/lib" } ``` diff --git a/package.json b/package.json index f1e7fe1..0fa5b64 100644 --- a/package.json +++ b/package.json @@ -314,6 +314,22 @@ "description": "Preferred path ending for auto imports.", "scope": "resource" }, + "typescript.preferences.jsxAttributeCompletionStyle": { + "type": "string", + "enum": [ + "auto", + "braces", + "none" + ], + "markdownEnumDescriptions": [ + "Insert `={}` or `=\"\"` after attribute names based on the prop type.", + "Insert `={}` after attribute names.", + "Only insert attribute names." + ], + "default": "auto", + "description": "Preferred style for JSX attribute completions.", + "scope": "resource" + }, "typescript.preferences.quoteStyle": { "type": "string", "default": "auto", @@ -524,6 +540,22 @@ "description": "Preferred path ending for auto imports.", "scope": "resource" }, + "javascript.preferences.jsxAttributeCompletionStyle": { + "type": "string", + "enum": [ + "auto", + "braces", + "none" + ], + "markdownEnumDescriptions": [ + "Insert `={}` or `=\"\"` after attribute names based on the prop type.", + "Insert `={}` after attribute names.", + "Only insert attribute names." + ], + "default": "auto", + "description": "Preferred style for JSX attribute completions.", + "scope": "resource" + }, "javascript.preferences.quoteStyle": { "type": "string", "default": "auto", diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index d465cff..6d83e56 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -176,6 +176,8 @@ export default class FileConfigurationManager { quotePreference: this.getQuoteStyle(config), importModuleSpecifierPreference: getImportModuleSpecifier(config) as any, importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(config), + // @ts-expect-error until TS 4.5 protocol update + jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(config), allowTextChangesInNewFiles: uri.startsWith('file:'), allowRenameOfImportPath: true, providePrefixAndSuffixTextForRename: config.get('renameShorthandProperties', true) === false ? false : config.get('useAliasesForRenames', true), @@ -220,3 +222,11 @@ function getImportModuleSpecifierEndingPreference(config: WorkspaceConfiguration default: return 'auto' } } + +function getJsxAttributeCompletionStyle(config: WorkspaceConfiguration) { + switch (config.get('jsxAttributeCompletionStyle')) { + case 'braces': return 'braces' + case 'none': return 'none' + default: return 'auto' + } +} From 065abccd88cfa37f34e8e0a4979d7e8a28b26f50 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Mon, 20 Dec 2021 23:53:39 -0600 Subject: [PATCH 03/78] refactor: Disables type acquisition by default (#320) * docs: update yarn configuration (#311) * refactor: Disables type acquisition by default Co-authored-by: KY64 <31939494+KY64@users.noreply.github.com> --- Readme.md | 2 +- package.json | 2 +- src/server/utils/configuration.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 00d1483..b2a2696 100644 --- a/Readme.md +++ b/Readme.md @@ -126,7 +126,7 @@ for guide of coc.nvim's configuration. - `tsserver.implicitProjectConfig.experimentalDecorators`:Enable experimentalDecorators for implicit project, default: `false` - `tsserver.disableAutomaticTypeAcquisition`:Disable download of typings, - default: `false` + default: `true` - `tsserver.useBatchedBufferSync`: use batched buffer synchronize support. - `typescript.updateImportsOnFileMove.enable`:Enable update imports on file move., default: `true` diff --git a/package.json b/package.json index 0fa5b64..103bd80 100644 --- a/package.json +++ b/package.json @@ -252,7 +252,7 @@ }, "tsserver.disableAutomaticTypeAcquisition": { "type": "boolean", - "default": false, + "default": true, "description": "Disable download of typings" }, "tsserver.useBatchedBufferSync": { diff --git a/src/server/utils/configuration.ts b/src/server/utils/configuration.ts index 7ee4c21..c23682b 100644 --- a/src/server/utils/configuration.ts +++ b/src/server/utils/configuration.ts @@ -86,7 +86,7 @@ export class TypeScriptServiceConfiguration { } public get disableAutomaticTypeAcquisition(): boolean { - return this._configuration.get('disableAutomaticTypeAcquisition', false) + return this._configuration.get('disableAutomaticTypeAcquisition', true) } public get formatOnType(): boolean { From 2d52a842cbde2dff1e1504cdba1f98ad193673ed Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Tue, 21 Dec 2021 13:54:47 +0800 Subject: [PATCH 04/78] feat: includeCompletionsWithClassMemberSnippets (#321) * docs: update yarn configuration (#311) * feat: includeCompletionsWithClassMemberSnippets typescript.suggest.includeCompletionsWithClassMemberSnippets Co-authored-by: KY64 <31939494+KY64@users.noreply.github.com> --- Readme.md | 3 +++ package.json | 6 ++++++ src/server/features/completionItemProvider.ts | 2 +- src/server/features/fileConfigurationManager.ts | 3 +++ 4 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index b2a2696..a5c9ade 100644 --- a/Readme.md +++ b/Readme.md @@ -156,6 +156,9 @@ for guide of coc.nvim's configuration. TypeScript 4.3+ in the workspace, default: `true` - `typescript.suggest.includeCompletionsWithSnippetText`: Enable snippet completions from TS Server. Requires using TypeScript 4.3+ in the workspace, default: `true` +- `typescript.suggest.includeCompletionsWithClassMemberSnippets`: Enable/disable + snippet completions for class members. Requires using TypeScript 4.5+ in the + workspace, default: `true` - `typescript.format.enabled`:Enable/disable format of typescript files. - `typescript.format.insertSpaceAfterCommaDelimiter` default: `true` - `typescript.format.insertSpaceAfterConstructor` default: `false` diff --git a/package.json b/package.json index 103bd80..029a1d2 100644 --- a/package.json +++ b/package.json @@ -390,6 +390,12 @@ "description": "Enable/disable snippet completions from TS Server. Requires using TypeScript 4.3+ in the workspace.", "scope": "resource" }, + "typescript.suggest.includeCompletionsWithClassMemberSnippets": { + "type": "boolean", + "default": true, + "description": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace", + "scope": "resource" + }, "typescript.format.enabled": { "type": "boolean", "default": true, diff --git a/src/server/features/completionItemProvider.ts b/src/server/features/completionItemProvider.ts index 7f2ad21..25733cf 100644 --- a/src/server/features/completionItemProvider.ts +++ b/src/server/features/completionItemProvider.ts @@ -259,7 +259,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP item.additionalTextEdits = additionalTextEdits if (detail && item.insertTextFormat == InsertTextFormat.Snippet) { const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, position, token) - if (shouldCompleteFunction) { + if (shouldCompleteFunction && !item.insertText) { this.createSnippetOfFunctionCall(item, detail) } } diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 6d83e56..8c6e796 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -40,6 +40,7 @@ export interface SuggestOptions { readonly importStatementSuggestions: boolean readonly includeCompletionsForImportStatements: boolean readonly includeCompletionsWithSnippetText: boolean + readonly includeCompletionsWithClassMemberSnippets: boolean } export default class FileConfigurationManager { @@ -162,6 +163,7 @@ export default class FileConfigurationManager { importStatementSuggestions: config.get('importStatements', true), includeCompletionsForImportStatements: config.get('includeCompletionsForImportStatements', true), includeCompletionsWithSnippetText: config.get('includeCompletionsWithSnippetText', true), + includeCompletionsWithClassMemberSnippets: config.get('includeCompletionsWithClassMemberSnippets', true), includeAutomaticOptionalChainCompletions: config.get('includeAutomaticOptionalChainCompletions', true) } } @@ -182,6 +184,7 @@ export default class FileConfigurationManager { allowRenameOfImportPath: true, providePrefixAndSuffixTextForRename: config.get('renameShorthandProperties', true) === false ? false : config.get('useAliasesForRenames', true), includeCompletionsForImportStatements: this.getCompleteOptions(language).includeCompletionsForImportStatements, + includeCompletionsWithClassMemberSnippets: this.getCompleteOptions(language).includeCompletionsWithClassMemberSnippets, includeCompletionsWithSnippetText: this.getCompleteOptions(language).includeCompletionsWithSnippetText, } return preferences From 95321f24c9e6063b388bf30818fcf97b61dd2ab3 Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Tue, 21 Dec 2021 13:56:01 +0800 Subject: [PATCH 05/78] feat: add tsserver.sortImports (#322) * docs: update yarn configuration (#311) * feat: add tsserver.sortImports add source.sortImports codeAction that used in editor.action.sourceAction coc.nvim can add editor.action.sourceAction Co-authored-by: KY64 <31939494+KY64@users.noreply.github.com> --- src/index.ts | 3 ++- src/server/organizeImports.ts | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index cdd0936..18f2845 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { commands, ExtensionContext, services, workspace } from 'coc.nvim' import TsserverService from './server' import { AutoFixCommand, Command, ConfigurePluginCommand, FileReferencesCommand, OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './server/commands' -import { OrganizeImportsCommand } from './server/organizeImports' +import { OrganizeImportsCommand, SourceImportsCommand } from './server/organizeImports' import { PluginManager } from './utils/plugins' interface API { @@ -25,6 +25,7 @@ export async function activate(context: ExtensionContext): Promise { registCommand(new OpenTsServerLogCommand(service)) registCommand(new TypeScriptGoToProjectConfigCommand(service)) registCommand(new OrganizeImportsCommand(service)) + registCommand(new SourceImportsCommand(service)) registCommand({ id: 'tsserver.restart', execute: (): void => { diff --git a/src/server/organizeImports.ts b/src/server/organizeImports.ts index 4cca6c7..8f249af 100644 --- a/src/server/organizeImports.ts +++ b/src/server/organizeImports.ts @@ -19,9 +19,10 @@ export class OrganizeImportsCommand implements Command { ) { } - private async _execute(client: TypeScriptServiceClient, document: TextDocument): Promise { + private async _execute(client: TypeScriptServiceClient, document: TextDocument, sortOnly = false): Promise { let file = client.toPath(document.uri) const args: Proto.OrganizeImportsRequestArgs = { + skipDestructiveCodeActions: sortOnly, scope: { type: 'file', args: { @@ -49,7 +50,7 @@ export class OrganizeImportsCommand implements Command { if (edit) await workspace.applyEdit(edit) } - public async execute(document?: TextDocument): Promise { + public async execute(document?: TextDocument, sortOnly = false): Promise { let client = await this.service.getClientHost() if (!document) { let doc = await workspace.document @@ -61,10 +62,14 @@ export class OrganizeImportsCommand implements Command { } document = doc.textDocument } - await this._execute(client.serviceClient, document) + await this._execute(client.serviceClient, document, sortOnly) } } +export class SourceImportsCommand extends OrganizeImportsCommand { + public readonly id = 'tsserver.sortImports' +} + export class OrganizeImportsCodeActionProvider implements CodeActionProvider { // public static readonly minVersion = API.v280 @@ -91,11 +96,16 @@ export class OrganizeImportsCodeActionProvider implements CodeActionProvider { } await this.fileConfigManager.ensureConfigurationForDocument(document, token) - const action = CodeAction.create('Organize Imports', { + const organizeImportsAction = CodeAction.create('Organize Imports', { title: '', command: 'tsserver.organizeImports', arguments: [document] }, CodeActionKind.SourceOrganizeImports) - return [action] + const sortImportsAction = CodeAction.create('Sort Imports', { + title: '', + command: 'tsserver.sortImports', + arguments: [document, true] + }, 'source.sortImports') + return [organizeImportsAction, sortImportsAction] } } From e59643c97b4596734c8fc1ee1aeb648c75b70d5d Mon Sep 17 00:00:00 2001 From: Carlo Sala Date: Tue, 21 Dec 2021 06:57:51 +0100 Subject: [PATCH 06/78] docs: typos and adding some missing defaults (#326) * docs: update yarn configuration (#311) * docs: typos and adding some missing defaults Co-authored-by: KY64 <31939494+KY64@users.noreply.github.com> Co-authored-by: Qiming zhao --- Readme.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Readme.md b/Readme.md index a5c9ade..0300baa 100644 --- a/Readme.md +++ b/Readme.md @@ -29,7 +29,7 @@ tsserver understand your code. installation. **Note:** tsserver could be quite slow to initialize on big project, exclude -unneunnecessary files in your jsconfig.json/tsconfig.json. +unnecessary files in your jsconfig.json/tsconfig.json. **Note:** if you're using WSL, copy you project files from mounted dirs to linux home otherwise tsserver will not work properly. @@ -119,15 +119,15 @@ for guide of coc.nvim's configuration. - `tsserver.debugPort`:Debug port number of tsserver - `tsserver.watchOptions`:Configure which watching strategies should be used to keep track of files and directories. Requires using TypeScript 3.8+ in the - workspace, default: undefined. + workspace, default: `undefined` - `tsserver.reportStyleChecksAsWarnings` default: `true` - `tsserver.implicitProjectConfig.checkJs`:Enable checkJs for implicit project, default: `false` - `tsserver.implicitProjectConfig.experimentalDecorators`:Enable experimentalDecorators for implicit project, default: `false` - `tsserver.disableAutomaticTypeAcquisition`:Disable download of typings, - default: `true` -- `tsserver.useBatchedBufferSync`: use batched buffer synchronize support. + default: `false` +- `tsserver.useBatchedBufferSync`: use batched buffer synchronize support, default: `true` - `typescript.updateImportsOnFileMove.enable`:Enable update imports on file move., default: `true` - `typescript.implementationsCodeLens.enable`:Enable codeLens for @@ -143,11 +143,11 @@ for guide of coc.nvim's configuration. - `typescript.validate.enable`:Enable/disable TypeScript validation., default: `true` - `typescript.showUnused`: show unused variable hint, default: `true`. -- `typescript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false`. +- `typescript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false` - `typescript.suggest.enabled` default: `true` - `typescript.suggest.paths`:Enable/disable suggest paths in import statement and require calls, default: `true` -- `typescript.suggest.autoImports`:Enable/disable auto import suggests., +- `typescript.suggest.autoImports`:Enable/disable auto import suggests, default: `true` - `typescript.suggest.completeFunctionCalls`:Enable snippet for method suggestion, default: `true` @@ -185,9 +185,9 @@ for guide of coc.nvim's configuration. - `typescript.format.placeOpenBraceOnNewLineForFunctions` default: `false` - `typescript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false` - `typescript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` -- `javascript.format.enabled`: Enable/disable format for javascript files. -- `javascript.showUnused`: show unused variable hint. -- `javascript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false`. +- `javascript.format.enabled`: Enable/disable format for javascript files, default: `true` +- `javascript.showUnused`: show unused variable hint, default: `true` +- `javascript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false` - `javascript.updateImportsOnFileMove.enable` default: `true` - `javascript.implementationsCodeLens.enable` default: `true` - `javascript.referencesCodeLens.enable` default: `true` From 4b662b57e4bc6d448b6e35d77c80ddd72e289cc2 Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Tue, 21 Dec 2021 13:48:41 +0800 Subject: [PATCH 07/78] feat: add semanticTokens support (#313) --- src/server/features/semanticTokens.ts | 288 ++++++++++++++++++++++++++ src/server/languageProvider.ts | 7 +- src/server/utils/api.ts | 1 + 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 src/server/features/semanticTokens.ts diff --git a/src/server/features/semanticTokens.ts b/src/server/features/semanticTokens.ts new file mode 100644 index 0000000..683f64f --- /dev/null +++ b/src/server/features/semanticTokens.ts @@ -0,0 +1,288 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, Range, SemanticTokens, SemanticTokensBuilder, TextDocument, workspace } from 'coc.nvim' +import { SemanticTokensLegend } from 'vscode-languageserver-protocol' +import * as Proto from '../protocol' +import { ExecConfig, ITypeScriptServiceClient, ServerResponse } from '../typescriptService' +import API from '../utils/api' + +// as we don't do deltas, for performance reasons, don't compute semantic tokens for documents above that limit +const CONTENT_LENGTH_LIMIT = 100000 + +/** + * Prototype of a DocumentSemanticTokensProvider, relying on the experimental `encodedSemanticClassifications-full` request from the TypeScript server. + * As the results retured by the TypeScript server are limited, we also add a Typescript plugin (typescript-vscode-sh-plugin) to enrich the returned token. + * See https://github.com/aeschli/typescript-vscode-sh-plugin. + */ +export default class TypeScriptDocumentSemanticTokensProvider implements DocumentSemanticTokensProvider, DocumentRangeSemanticTokensProvider { + public static readonly minVersion = API.v370 + + constructor(private readonly client: ITypeScriptServiceClient) {} + + getLegend(): SemanticTokensLegend { + return { + tokenTypes, + tokenModifiers + } + } + + async provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): Promise { + const file = this.client.toOpenedFilePath(document.uri) + if (!file || document.getText().length > CONTENT_LENGTH_LIMIT) { + return null + } + return this._provideSemanticTokens(document, { file, start: 0, length: document.getText().length }, token) + } + + async provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): Promise { + const file = this.client.toOpenedFilePath(document.uri) + if (!file || (document.offsetAt(range.end) - document.offsetAt(range.start) > CONTENT_LENGTH_LIMIT)) { + return null + } + + const start = document.offsetAt(range.start) + const length = document.offsetAt(range.end) - start + return this._provideSemanticTokens(document, { file, start, length }, token) + } + + async _provideSemanticTokens(document: TextDocument, requestArg: Proto.EncodedSemanticClassificationsRequestArgs, token: CancellationToken): Promise { + const file = this.client.toOpenedFilePath(document.uri) + if (!file) { + return null + } + + const versionBeforeRequest = document.version + + requestArg.format = '2020' + + const response = await (this.client as ExperimentalProtocol.IExtendedTypeScriptServiceClient).execute('encodedSemanticClassifications-full', requestArg, token, { + cancelOnResourceChange: document.uri + }) + if (response.type !== 'response' || !response.body) { + return null + } + + const versionAfterRequest = document.version + if (versionBeforeRequest !== versionAfterRequest) { + // cannot convert result's offsets to (linecol) values correctly + // a new request will come in soon... + // + // here we cannot return null, because returning null would remove all semantic tokens. + // we must throw to indicate that the semantic tokens should not be removed. + // using the string busy here because it is not logged to error telemetry if the error text contains busy. + + // as the new request will come in right after our response, we first wait for the document activity to stop + await waitForDocumentChangesToEnd(document) + + throw new Error('Canceled') + } + + const doc = workspace.getDocument(document.uri) + const tokenSpan = response.body.spans + + const builder = new SemanticTokensBuilder() + let i = 0 + while (i < tokenSpan.length) { + const offset = tokenSpan[i++] + const length = tokenSpan[i++] + const tsClassification = tokenSpan[i++] + + let tokenModifiers = 0 + let tokenType = getTokenTypeFromClassification(tsClassification) + if (tokenType !== undefined) { + // it's a classification as returned by the typescript-vscode-sh-plugin + tokenModifiers = getTokenModifierFromClassification(tsClassification) + } else { + // typescript-vscode-sh-plugin is not present + tokenType = tokenTypeMap[tsClassification] + if (tokenType === undefined) { + continue + } + } + + // we can use the document's range conversion methods because the result is at the same version as the document + const startPos = document.positionAt(offset) + const endPos = document.positionAt(offset + length) + for (let line = startPos.line; line <= endPos.line; line++) { + const startCharacter = (line === startPos.line ? startPos.character : 0) + const endCharacter = (line === endPos.line ? endPos.character : doc.getline(line).length) + builder.push(line, startCharacter, endCharacter - startCharacter, tokenType, tokenModifiers) + } + } + return builder.build() + } +} + +function waitForDocumentChangesToEnd(document: TextDocument) { + let version = document.version + return new Promise((s) => { + const iv = setInterval(_ => { + if (document.version === version) { + clearInterval(iv) + s() + } + version = document.version + }, 400) + }) +} + +function getTokenTypeFromClassification(tsClassification: number): number | undefined { + if (tsClassification > TokenEncodingConsts.modifierMask) { + return (tsClassification >> TokenEncodingConsts.typeOffset) - 1 + } + return undefined +} + +function getTokenModifierFromClassification(tsClassification: number) { + return tsClassification & TokenEncodingConsts.modifierMask +} + +// typescript encodes type and modifiers in the classification: +// TSClassification = (TokenType + 1) << 8 + TokenModifier + +const enum TokenType { + class = 0, + enum = 1, + interface = 2, + namespace = 3, + typeParameter = 4, + type = 5, + parameter = 6, + variable = 7, + enumMember = 8, + property = 9, + function = 10, + method = 11, + _ = 12 +} +const enum TokenModifier { + declaration = 0, + static = 1, + async = 2, + readonly = 3, + defaultLibrary = 4, + local = 5, + _ = 6 +} +const enum TokenEncodingConsts { + typeOffset = 8, + modifierMask = 255 +} + +const tokenTypes: string[] = [] +tokenTypes[TokenType.class] = 'class' +tokenTypes[TokenType.enum] = 'enum' +tokenTypes[TokenType.interface] = 'interface' +tokenTypes[TokenType.namespace] = 'namespace' +tokenTypes[TokenType.typeParameter] = 'typeParameter' +tokenTypes[TokenType.type] = 'type' +tokenTypes[TokenType.parameter] = 'parameter' +tokenTypes[TokenType.variable] = 'variable' +tokenTypes[TokenType.enumMember] = 'enumMember' +tokenTypes[TokenType.property] = 'property' +tokenTypes[TokenType.function] = 'function' +tokenTypes[TokenType.method] = 'method' + +const tokenModifiers: string[] = [] +tokenModifiers[TokenModifier.async] = 'async' +tokenModifiers[TokenModifier.declaration] = 'declaration' +tokenModifiers[TokenModifier.readonly] = 'readonly' +tokenModifiers[TokenModifier.static] = 'static' +tokenModifiers[TokenModifier.local] = 'local' +tokenModifiers[TokenModifier.defaultLibrary] = 'defaultLibrary' + +export namespace ExperimentalProtocol { + + export interface IExtendedTypeScriptServiceClient { + execute( + command: K, + args: ExperimentalProtocol.ExtendedTsServerRequests[K][0], + token: CancellationToken, + config?: ExecConfig + ): Promise> + } + + /** + * A request to get encoded semantic classifications for a span in the file + */ + export interface EncodedSemanticClassificationsRequest extends Proto.FileRequest { + arguments: EncodedSemanticClassificationsRequestArgs + } + + /** + * Arguments for EncodedSemanticClassificationsRequest request. + */ + export interface EncodedSemanticClassificationsRequestArgs extends Proto.FileRequestArgs { + /** + * Start position of the span. + */ + start: number + /** + * Length of the span. + */ + length: number + } + + export const enum EndOfLineState { + None, + InMultiLineCommentTrivia, + InSingleQuoteStringLiteral, + InDoubleQuoteStringLiteral, + InTemplateHeadOrNoSubstitutionTemplate, + InTemplateMiddleOrTail, + InTemplateSubstitutionPosition, + } + + export const enum ClassificationType { + comment = 1, + identifier = 2, + keyword = 3, + numericLiteral = 4, + operator = 5, + stringLiteral = 6, + regularExpressionLiteral = 7, + whiteSpace = 8, + text = 9, + punctuation = 10, + className = 11, + enumName = 12, + interfaceName = 13, + moduleName = 14, + typeParameterName = 15, + typeAliasName = 16, + parameterName = 17, + docCommentTagName = 18, + jsxOpenTagName = 19, + jsxCloseTagName = 20, + jsxSelfClosingTagName = 21, + jsxAttribute = 22, + jsxText = 23, + jsxAttributeStringLiteralValue = 24, + bigintLiteral = 25, + } + + export interface EncodedSemanticClassificationsResponse extends Proto.Response { + body?: { + endOfLineState: EndOfLineState + spans: number[] + } + } + + export interface ExtendedTsServerRequests { + 'encodedSemanticClassifications-full': [ExperimentalProtocol.EncodedSemanticClassificationsRequestArgs, ExperimentalProtocol.EncodedSemanticClassificationsResponse] + } +} + +// mapping for the original ExperimentalProtocol.ClassificationType from TypeScript (only used when plugin is not available) +const tokenTypeMap: number[] = [] +tokenTypeMap[ExperimentalProtocol.ClassificationType.className] = TokenType.class +tokenTypeMap[ExperimentalProtocol.ClassificationType.enumName] = TokenType.enum +tokenTypeMap[ExperimentalProtocol.ClassificationType.interfaceName] = TokenType.interface +tokenTypeMap[ExperimentalProtocol.ClassificationType.moduleName] = TokenType.namespace +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeParameterName] = TokenType.typeParameter +tokenTypeMap[ExperimentalProtocol.ClassificationType.typeAliasName] = TokenType.type +tokenTypeMap[ExperimentalProtocol.ClassificationType.parameterName] = TokenType.parameter + diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index efdb855..5ab9072 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -26,6 +26,7 @@ import ReferenceProvider from './features/references' import ReferencesCodeLensProvider from './features/referencesCodeLens' import RenameProvider from './features/rename' import SignatureHelpProvider from './features/signatureHelp' +import SemanticTokensProvider from './features/semanticTokens' import SmartSelection from './features/smartSelect' import TagClosing from './features/tagClosing' import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' @@ -105,9 +106,13 @@ export default class LanguageProvider { this._register(languages.registerDocumentRangeFormatProvider(languageIds, formatProvider)) this._register(languages.registerOnTypeFormattingEditProvider(languageIds, formatProvider, [';', '}', '\n', String.fromCharCode(27)])) this._register(languages.registerCodeActionProvider(languageIds, new InstallModuleProvider(client), 'tsserver')) - if (typeof languages['registerCallHierarchyProvider'] === 'function') { + if (this.client.apiVersion.gte(API.v380) && typeof languages['registerCallHierarchyProvider'] === 'function') { this._register(languages.registerCallHierarchyProvider(languageIds, new CallHierarchyProvider(client))) } + if (this.client.apiVersion.gte(API.v370) && typeof languages['registerDocumentSemanticTokensProvider'] === 'function') { + const provider = new SemanticTokensProvider(client) + this._register(languages.registerDocumentSemanticTokensProvider(languageIds, provider, provider.getLegend())) + } let { fileConfigurationManager } = this let conf = fileConfigurationManager.getLanguageConfiguration(this.id) diff --git a/src/server/utils/api.ts b/src/server/utils/api.ts index 6f6008a..9e0dc5a 100644 --- a/src/server/utils/api.ts +++ b/src/server/utils/api.ts @@ -35,6 +35,7 @@ export default class API { public static readonly v340 = API.fromSimpleString('3.4.0') public static readonly v345 = API.fromSimpleString('3.4.5') public static readonly v350 = API.fromSimpleString('3.5.0') + public static readonly v370 = API.fromSimpleString('3.7.0') public static readonly v380 = API.fromSimpleString('3.8.0') public static readonly v381 = API.fromSimpleString('3.8.1') public static readonly v390 = API.fromSimpleString('3.9.0') From 61578a5074943324062e4788fd84b8c468568484 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 21 Dec 2021 16:12:54 +0800 Subject: [PATCH 08/78] feat: register range semantic tokens provider --- src/server/languageProvider.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 5ab9072..26368d2 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -109,9 +109,14 @@ export default class LanguageProvider { if (this.client.apiVersion.gte(API.v380) && typeof languages['registerCallHierarchyProvider'] === 'function') { this._register(languages.registerCallHierarchyProvider(languageIds, new CallHierarchyProvider(client))) } - if (this.client.apiVersion.gte(API.v370) && typeof languages['registerDocumentSemanticTokensProvider'] === 'function') { + if (this.client.apiVersion.gte(API.v370)) { const provider = new SemanticTokensProvider(client) - this._register(languages.registerDocumentSemanticTokensProvider(languageIds, provider, provider.getLegend())) + if (typeof languages['registerDocumentSemanticTokensProvider'] === 'function') { + this._register(languages.registerDocumentSemanticTokensProvider(languageIds, provider, provider.getLegend())) + } + if (typeof languages['registerDocumentRangeSemanticTokensProvider'] === 'function') { + this._register(languages.registerDocumentRangeSemanticTokensProvider(languageIds, provider, provider.getLegend())) + } } let { fileConfigurationManager } = this From 30a23e2454042983b8ca2c3ad8988a42230311a0 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 21 Dec 2021 17:00:31 +0800 Subject: [PATCH 09/78] chore(package): upgrade typescript module --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 029a1d2..69f5bba 100644 --- a/package.json +++ b/package.json @@ -730,6 +730,6 @@ "which": "^2.0.2" }, "dependencies": { - "typescript": "^4.3.5" + "typescript": "^4.5.4" } } diff --git a/yarn.lock b/yarn.lock index ba5ce3f..f1555d9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -27,10 +27,10 @@ semver@^7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -typescript@^4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" - integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== +typescript@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" + integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== vscode-jsonrpc@6.0.0: version "6.0.0" From cd16da88ef794f2da07d634011e6c42cb878a8f2 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 21 Dec 2021 17:00:49 +0800 Subject: [PATCH 10/78] feat: support suggest.classMemberSnippets.enabled configuration --- Readme.md | 5 ++++- package.json | 8 +++++++- src/server/features/fileConfigurationManager.ts | 10 +++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Readme.md b/Readme.md index 0300baa..c9229fb 100644 --- a/Readme.md +++ b/Readme.md @@ -156,7 +156,7 @@ for guide of coc.nvim's configuration. TypeScript 4.3+ in the workspace, default: `true` - `typescript.suggest.includeCompletionsWithSnippetText`: Enable snippet completions from TS Server. Requires using TypeScript 4.3+ in the workspace, default: `true` -- `typescript.suggest.includeCompletionsWithClassMemberSnippets`: Enable/disable +- `typescript.suggest.classMemberSnippets.enabled`: Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace, default: `true` - `typescript.format.enabled`:Enable/disable format of typescript files. @@ -210,6 +210,9 @@ for guide of coc.nvim's configuration. - `javascript.suggest.includeCompletionsForImportStatements`: Enable/disable auto-import-style completions on partially-typed import statements. Requires using TypeScript 4.3+ in the workspace, default: `true` +- `javascript.suggest.classMemberSnippets.enabled`: Enable/disable + snippet completions for class members. Requires using TypeScript 4.5+ in the + workspace, default: `true` - `javascript.format.insertSpaceAfterCommaDelimiter` default: `true` - `javascript.format.insertSpaceAfterConstructor` default: `false` - `javascript.format.insertSpaceAfterSemicolonInForStatements` default: `true` diff --git a/package.json b/package.json index 69f5bba..ea34e65 100644 --- a/package.json +++ b/package.json @@ -390,7 +390,7 @@ "description": "Enable/disable snippet completions from TS Server. Requires using TypeScript 4.3+ in the workspace.", "scope": "resource" }, - "typescript.suggest.includeCompletionsWithClassMemberSnippets": { + "typescript.suggest.classMemberSnippets.enabled": { "type": "boolean", "default": true, "description": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace", @@ -616,6 +616,12 @@ "description": "Enable/disable auto-import-style completions on partially-typed import statements. Requires using TypeScript 4.3+ in the workspace.", "scope": "resource" }, + "javascript.suggest.classMemberSnippets.enabled": { + "type": "boolean", + "default": true, + "description": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace", + "scope": "resource" + }, "javascript.format.enabled": { "type": "boolean", "default": true, diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 8c6e796..7a6635d 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -163,7 +163,7 @@ export default class FileConfigurationManager { importStatementSuggestions: config.get('importStatements', true), includeCompletionsForImportStatements: config.get('includeCompletionsForImportStatements', true), includeCompletionsWithSnippetText: config.get('includeCompletionsWithSnippetText', true), - includeCompletionsWithClassMemberSnippets: config.get('includeCompletionsWithClassMemberSnippets', true), + includeCompletionsWithClassMemberSnippets: config.get('classMemberSnippets.enabled', true), includeAutomaticOptionalChainCompletions: config.get('includeAutomaticOptionalChainCompletions', true) } } @@ -173,19 +173,19 @@ export default class FileConfigurationManager { return {} } const config = workspace.getConfiguration(`${language}.preferences`, uri) + const suggestConfig = this.getCompleteOptions(language) // getImportModuleSpecifierEndingPreference available on ts 2.9.0 const preferences: Proto.UserPreferences & { importModuleSpecifierEnding?: string } = { quotePreference: this.getQuoteStyle(config), importModuleSpecifierPreference: getImportModuleSpecifier(config) as any, importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(config), - // @ts-expect-error until TS 4.5 protocol update jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(config), allowTextChangesInNewFiles: uri.startsWith('file:'), allowRenameOfImportPath: true, providePrefixAndSuffixTextForRename: config.get('renameShorthandProperties', true) === false ? false : config.get('useAliasesForRenames', true), - includeCompletionsForImportStatements: this.getCompleteOptions(language).includeCompletionsForImportStatements, - includeCompletionsWithClassMemberSnippets: this.getCompleteOptions(language).includeCompletionsWithClassMemberSnippets, - includeCompletionsWithSnippetText: this.getCompleteOptions(language).includeCompletionsWithSnippetText, + includeCompletionsForImportStatements: suggestConfig.includeCompletionsForImportStatements, + includeCompletionsWithClassMemberSnippets: suggestConfig.includeCompletionsWithClassMemberSnippets, + includeCompletionsWithSnippetText: suggestConfig.includeCompletionsWithSnippetText, } return preferences } From 5a8c68fc60e5449b9b7a6e1093ca68e98c2179bf Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 21 Dec 2021 17:22:46 +0800 Subject: [PATCH 11/78] Add suggest.jsdoc.generateReturns configuration. --- Readme.md | 6 ++++++ package.json | 12 ++++++++++++ src/server/features/fileConfigurationManager.ts | 5 +++++ 3 files changed, 23 insertions(+) diff --git a/Readme.md b/Readme.md index c9229fb..6fc895c 100644 --- a/Readme.md +++ b/Readme.md @@ -159,6 +159,9 @@ for guide of coc.nvim's configuration. - `typescript.suggest.classMemberSnippets.enabled`: Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace, default: `true` +- `typescript.suggest.jsdoc.generateReturns`: Enable/disable generating + `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in + the workspace. default: `true` - `typescript.format.enabled`:Enable/disable format of typescript files. - `typescript.format.insertSpaceAfterCommaDelimiter` default: `true` - `typescript.format.insertSpaceAfterConstructor` default: `false` @@ -210,6 +213,9 @@ for guide of coc.nvim's configuration. - `javascript.suggest.includeCompletionsForImportStatements`: Enable/disable auto-import-style completions on partially-typed import statements. Requires using TypeScript 4.3+ in the workspace, default: `true` +- `javascript.suggest.jsdoc.generateReturns`: Enable/disable generating + `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in + the workspace. default: `true` - `javascript.suggest.classMemberSnippets.enabled`: Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace, default: `true` diff --git a/package.json b/package.json index ea34e65..48f4487 100644 --- a/package.json +++ b/package.json @@ -396,6 +396,12 @@ "description": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace", "scope": "resource" }, + "typescript.suggest.jsdoc.generateReturns": { + "type": "boolean", + "default": true, + "markdownDescription": "Enable/disable generating `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in the workspace.", + "scope": "resource" + }, "typescript.format.enabled": { "type": "boolean", "default": true, @@ -622,6 +628,12 @@ "description": "Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace", "scope": "resource" }, + "javascript.suggest.jsdoc.generateReturns": { + "type": "boolean", + "default": true, + "markdownDescription": "Enable/disable generating `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in the workspace.", + "scope": "resource" + }, "javascript.format.enabled": { "type": "boolean", "default": true, diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 7a6635d..1f39d28 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -41,6 +41,7 @@ export interface SuggestOptions { readonly includeCompletionsForImportStatements: boolean readonly includeCompletionsWithSnippetText: boolean readonly includeCompletionsWithClassMemberSnippets: boolean + readonly generateReturnInDocTemplate: boolean } export default class FileConfigurationManager { @@ -160,6 +161,7 @@ export default class FileConfigurationManager { paths: config.get('paths', true), completeFunctionCalls: config.get('completeFunctionCalls', true), autoImports: config.get('autoImports', true), + generateReturnInDocTemplate: config.get('jsdoc.generateReturns', true), importStatementSuggestions: config.get('importStatements', true), includeCompletionsForImportStatements: config.get('includeCompletionsForImportStatements', true), includeCompletionsWithSnippetText: config.get('includeCompletionsWithSnippetText', true), @@ -182,7 +184,10 @@ export default class FileConfigurationManager { jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(config), allowTextChangesInNewFiles: uri.startsWith('file:'), allowRenameOfImportPath: true, + // can't support it with coc.nvim by now. + provideRefactorNotApplicableReason: false, providePrefixAndSuffixTextForRename: config.get('renameShorthandProperties', true) === false ? false : config.get('useAliasesForRenames', true), + generateReturnInDocTemplate: suggestConfig.generateReturnInDocTemplate, includeCompletionsForImportStatements: suggestConfig.includeCompletionsForImportStatements, includeCompletionsWithClassMemberSnippets: suggestConfig.includeCompletionsWithClassMemberSnippets, includeCompletionsWithSnippetText: suggestConfig.includeCompletionsWithSnippetText, From 4d78b618e4cf1276a10e9c91841b129c6acc0e1d Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 21 Dec 2021 17:38:28 +0800 Subject: [PATCH 12/78] add typescript.preferences.includePackageJsonAutoImports configuration --- Readme.md | 3 +++ package.json | 16 ++++++++++++++++ src/server/typescriptServiceClient.ts | 1 + src/server/utils/configuration.ts | 7 +++++++ 4 files changed, 27 insertions(+) diff --git a/Readme.md b/Readme.md index 6fc895c..119aec0 100644 --- a/Readme.md +++ b/Readme.md @@ -137,6 +137,9 @@ for guide of coc.nvim's configuration. - `typescript.preferences.importModuleSpecifier` default: `"auto"` - `typescript.preferences.importModuleSpecifierEnding` default: `"auto"` - `typescript.preferences.quoteStyle` default: `"single"` +- `typescript.preferences.includePackageJsonAutoImports`: Enable/disable + searching `package.json` dependencies for available auto imports, default: + `"auto"` - `typescript.suggestionActions.enabled`:Enable/disable suggestion diagnostics for TypeScript files in the editor. Requires using TypeScript 2.8 or newer in the workspace., default: `true` diff --git a/package.json b/package.json index 48f4487..191f5d7 100644 --- a/package.json +++ b/package.json @@ -330,6 +330,22 @@ "description": "Preferred style for JSX attribute completions.", "scope": "resource" }, + "typescript.preferences.includePackageJsonAutoImports": { + "type": "string", + "enum": [ + "auto", + "on", + "off" + ], + "enumDescriptions": [ + "Search dependencies based on estimated performance impact.", + "Always search dependencies.", + "Never search dependencies." + ], + "default": "auto", + "markdownDescription": "Enable/disable searching `package.json` dependencies for available auto imports.", + "scope": "window" + }, "typescript.preferences.quoteStyle": { "type": "string", "default": "auto", diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index 301d7f4..b1fb75d 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -408,6 +408,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient preferences: { providePrefixAndSuffixTextForRename: true, allowRenameOfImportPath: true, + includePackageJsonAutoImports: this._configuration.includePackageJsonAutoImports }, watchOptions } diff --git a/src/server/utils/configuration.ts b/src/server/utils/configuration.ts index c23682b..ec6b6ee 100644 --- a/src/server/utils/configuration.ts +++ b/src/server/utils/configuration.ts @@ -40,14 +40,21 @@ export namespace TsServerLogLevel { export class TypeScriptServiceConfiguration { private _configuration: WorkspaceConfiguration + private _includePackageJsonAutoImports: 'auto' | 'on' | 'off' private constructor() { this._configuration = workspace.getConfiguration('tsserver') + this._includePackageJsonAutoImports = workspace.getConfiguration('typescript').get<'auto' | 'on' | 'off'>('preferences.includePackageJsonAutoImports') workspace.onDidChangeConfiguration(() => { this._configuration = workspace.getConfiguration('tsserver') + this._includePackageJsonAutoImports = workspace.getConfiguration('typescript').get<'auto' | 'on' | 'off'>('preferences.includePackageJsonAutoImports') }) } + public get includePackageJsonAutoImports(): 'auto' | 'on' | 'off' { + return this._includePackageJsonAutoImports + } + public get locale(): string | null { return this._configuration.get('locale', null) } From 43e6f62e8746c110070f5d77fa125729a16e28c9 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 21 Dec 2021 18:04:20 +0800 Subject: [PATCH 13/78] add tsserver.enableTracing configuration --- Readme.md | 4 ++++ package.json | 6 ++++++ src/server/typescriptServiceClient.ts | 15 +++++++++++-- src/server/utils/configuration.ts | 4 ++++ src/server/utils/process.ts | 31 +++++++++++++++++++++------ 5 files changed, 52 insertions(+), 8 deletions(-) diff --git a/Readme.md b/Readme.md index 119aec0..03ffc52 100644 --- a/Readme.md +++ b/Readme.md @@ -128,6 +128,10 @@ for guide of coc.nvim's configuration. - `tsserver.disableAutomaticTypeAcquisition`:Disable download of typings, default: `false` - `tsserver.useBatchedBufferSync`: use batched buffer synchronize support, default: `true` +- `tsserver.enableTracing`: Enables tracing TS server performance to a + directory. These trace files can be used to diagnose TS Server performance + issues. The log may contain file paths, source code, and other potentially + sensitive information from your project, default: `false` - `typescript.updateImportsOnFileMove.enable`:Enable update imports on file move., default: `true` - `typescript.implementationsCodeLens.enable`:Enable codeLens for diff --git a/package.json b/package.json index 191f5d7..4b5ed22 100644 --- a/package.json +++ b/package.json @@ -224,6 +224,12 @@ ], "description": "Trace level of tsserver" }, + "tsserver.enableTracing": { + "type": "boolean", + "default": false, + "description": "Enables tracing TS server performance to a directory. These trace files can be used to diagnose TS Server performance issues. The log may contain file paths, source code, and other potentially sensitive information from your project.", + "scope": "window" + }, "tsserver.pluginPaths": { "type": "array", "default": [], diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index b1fb75d..6e99388 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -20,7 +20,7 @@ import { ExecConfig, ITypeScriptServiceClient, ServerResponse } from './typescri import API from './utils/api' import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration' import Logger from './utils/logger' -import { fork, getTempDirectory, getTempFile, IForkOptions, makeRandomHexString } from './utils/process' +import { fork, getTempDirectory, createTempDirectory, getTempFile, IForkOptions, makeRandomHexString } from './utils/process' import Tracer from './utils/tracer' import { inferredProjectConfig } from './utils/tsconfig' import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider' @@ -843,6 +843,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private async getTsServerArgs(currentVersion: TypeScriptVersion): Promise { const args: string[] = [] + args.push('--allowLocalPluginLoads') if (this.apiVersion.gte(API.v250)) { @@ -860,10 +861,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient args.push('--cancellationPipeName', this.cancellationPipeName + '*') } + const logDir = getTempDirectory() if (this.apiVersion.gte(API.v222)) { const isRoot = process.getuid && process.getuid() == 0 if (this._configuration.tsServerLogLevel !== TsServerLogLevel.Off && !isRoot) { - const logDir = getTempDirectory() if (logDir) { this.tsServerLogFile = path.join(logDir, `tsserver.log`) this.info('TSServer log file :', this.tsServerLogFile) @@ -882,6 +883,16 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } } + if (this._configuration.enableTsServerTracing) { + let tsServerTraceDirectory = createTempDirectory(`tsserver-trace-${makeRandomHexString(5)}`) + if (tsServerTraceDirectory) { + args.push('--traceDirectory', tsServerTraceDirectory) + this.info('TSServer trace directory :', tsServerTraceDirectory) + } else { + this.error('Could not create TSServer trace directory') + } + } + if (this.apiVersion.gte(API.v230)) { const pluginNames = this.pluginManager.plugins.map(x => x.name) let pluginPaths = this._configuration.tsServerPluginPaths diff --git a/src/server/utils/configuration.ts b/src/server/utils/configuration.ts index ec6b6ee..f6ae787 100644 --- a/src/server/utils/configuration.ts +++ b/src/server/utils/configuration.ts @@ -51,6 +51,10 @@ export class TypeScriptServiceConfiguration { }) } + public get enableTsServerTracing(): boolean { + return this._configuration.get('enableTracing', false) + } + public get includePackageJsonAutoImports(): 'auto' | 'on' | 'off' { return this._includePackageJsonAutoImports } diff --git a/src/server/utils/process.ts b/src/server/utils/process.ts index 3b8c6fd..ba2c2bb 100644 --- a/src/server/utils/process.ts +++ b/src/server/utils/process.ts @@ -24,10 +24,14 @@ export function makeRandomHexString(length: number): string { return result } -export function getTempDirectory(): string { +export function getTempDirectory(): string | undefined { let dir = path.join(os.tmpdir(), `coc.nvim-${process.pid}`) - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir) + try { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir) + } + } catch (e) { + return undefined } return dir } @@ -36,19 +40,34 @@ function generatePipeName(): string { return getPipeName(makeRandomHexString(40)) } -function getPipeName(name: string): string { +function getPipeName(name: string): string | undefined { const fullName = 'coc-tsc-' + name if (process.platform === 'win32') { return '\\\\.\\pipe\\' + fullName + '-sock' } const tmpdir = getTempDirectory() + if (!tmpdir) return undefined // Mac/Unix: use socket file return path.join(tmpdir, fullName + '.sock') } -export function getTempFile(name: string): string { +export function getTempFile(name: string): string | undefined { const fullName = 'coc-nvim-' + name - return path.join(getTempDirectory(), fullName + '.sock') + let dir = getTempDirectory() + if (!dir) return undefined + return path.join(dir, fullName + '.sock') +} + +export function createTempDirectory(name: string) { + let dir = getTempDirectory() + if (!dir) return undefined + let res = path.join(dir, name) + try { + fs.mkdirSync(res) + } catch (e) { + return undefined + } + return res } function generatePatchedEnv( From 3bd84b1ead087844fa45bf72dee00bfd44bd9aca Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 22 Dec 2021 01:17:42 +0800 Subject: [PATCH 14/78] Add typescript.check.npmIsInstalled configuration --- Readme.md | 3 +++ package.json | 8 +++++++- src/server/features/fileConfigurationManager.ts | 4 +++- src/server/utils/configuration.ts | 2 +- src/server/utils/typingsStatus.ts | 15 ++++++++++++--- 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index 03ffc52..baadd25 100644 --- a/Readme.md +++ b/Readme.md @@ -132,6 +132,9 @@ for guide of coc.nvim's configuration. directory. These trace files can be used to diagnose TS Server performance issues. The log may contain file paths, source code, and other potentially sensitive information from your project, default: `false` +- `typescript.check.npmIsInstalled`: Check if npm is installed for [Automatic + Type + Acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). - `typescript.updateImportsOnFileMove.enable`:Enable update imports on file move., default: `true` - `typescript.implementationsCodeLens.enable`:Enable codeLens for diff --git a/package.json b/package.json index 4b5ed22..ed90e4a 100644 --- a/package.json +++ b/package.json @@ -258,7 +258,7 @@ }, "tsserver.disableAutomaticTypeAcquisition": { "type": "boolean", - "default": true, + "default": false, "description": "Disable download of typings" }, "tsserver.useBatchedBufferSync": { @@ -266,6 +266,12 @@ "default": true, "description": "Use batched buffer sync support." }, + "typescript.check.npmIsInstalled": { + "type": "boolean", + "default": true, + "markdownDescription": "Check if npm is installed for [Automatic Type Acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition).", + "scope": "window" + }, "typescript.showUnused": { "type": "boolean", "default": true, diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 1f39d28..2b8141a 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -177,7 +177,7 @@ export default class FileConfigurationManager { const config = workspace.getConfiguration(`${language}.preferences`, uri) const suggestConfig = this.getCompleteOptions(language) // getImportModuleSpecifierEndingPreference available on ts 2.9.0 - const preferences: Proto.UserPreferences & { importModuleSpecifierEnding?: string } = { + const preferences: Proto.UserPreferences = { quotePreference: this.getQuoteStyle(config), importModuleSpecifierPreference: getImportModuleSpecifier(config) as any, importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(config), @@ -191,6 +191,8 @@ export default class FileConfigurationManager { includeCompletionsForImportStatements: suggestConfig.includeCompletionsForImportStatements, includeCompletionsWithClassMemberSnippets: suggestConfig.includeCompletionsWithClassMemberSnippets, includeCompletionsWithSnippetText: suggestConfig.includeCompletionsWithSnippetText, + allowIncompleteCompletions: true, + displayPartsForJSDoc: true, } return preferences } diff --git a/src/server/utils/configuration.ts b/src/server/utils/configuration.ts index f6ae787..bac12d9 100644 --- a/src/server/utils/configuration.ts +++ b/src/server/utils/configuration.ts @@ -97,7 +97,7 @@ export class TypeScriptServiceConfiguration { } public get disableAutomaticTypeAcquisition(): boolean { - return this._configuration.get('disableAutomaticTypeAcquisition', true) + return this._configuration.get('disableAutomaticTypeAcquisition', false) } public get formatOnType(): boolean { diff --git a/src/server/utils/typingsStatus.ts b/src/server/utils/typingsStatus.ts index 8adf8b9..d1608a5 100644 --- a/src/server/utils/typingsStatus.ts +++ b/src/server/utils/typingsStatus.ts @@ -1,4 +1,4 @@ -import { StatusBarItem, window } from 'coc.nvim' +import { StatusBarItem, workspace, window } from 'coc.nvim' /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. @@ -110,10 +110,19 @@ export class AtaProgressReporter { } } - private onTypesInstallerInitializationFailed() { // tslint:disable-line + private async onTypesInstallerInitializationFailed() { // tslint:disable-line this.statusItem.hide() if (!this._invalid) { - window.showMessage('Could not install typings files for JavaScript language features. Please ensure that NPM is installed', 'error') + const config = workspace.getConfiguration('typescript') + if (config.get('check.npmIsInstalled', true)) { + const dontShowAgain = "Don't Show Again" + const selected = await window.showWarningMessage( + "Could not install typings files for JavaScript language features. Please ensure that NPM is installed or configure 'typescript.npm' in your user settings. visit https://go.microsoft.com/fwlink/?linkid=847635 to learn more.", + dontShowAgain) + if (selected === dontShowAgain) { + config.update('check.npmIsInstalled', false, true) + } + } } this._invalid = true } From f0820d235710fa9b59a6d81327dbe7ce15d14ed9 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 22 Dec 2021 01:18:54 +0800 Subject: [PATCH 15/78] Release 1.9.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed90e4a..1391a7f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.8.6", + "version": "1.9.0", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 65e1f75be53ec4d9a50d0628544bcf4ce0974987 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 22 Dec 2021 14:24:04 +0800 Subject: [PATCH 16/78] refactor(server): use TSS_DEBUG & TSS_DEBUG_BRK for debug port --- Readme.md | 1 - package.json | 4 -- src/server/typescriptServiceClient.ts | 82 +++++++++++++++++---------- src/server/utils/configuration.ts | 4 -- src/server/utils/process.ts | 11 ++-- src/server/utils/wireProtocol.ts | 36 +++++++++--- 6 files changed, 82 insertions(+), 56 deletions(-) diff --git a/Readme.md b/Readme.md index baadd25..6c1e422 100644 --- a/Readme.md +++ b/Readme.md @@ -116,7 +116,6 @@ for guide of coc.nvim's configuration. - `tsserver.log`:Log level of tsserver, default: `"off"` - `tsserver.trace.server`:Trace level of tsserver, default: `"off"` - `tsserver.pluginPaths`:Folders contains tsserver plugins, default: `[]` -- `tsserver.debugPort`:Debug port number of tsserver - `tsserver.watchOptions`:Configure which watching strategies should be used to keep track of files and directories. Requires using TypeScript 3.8+ in the workspace, default: `undefined` diff --git a/package.json b/package.json index 1391a7f..8a27220 100644 --- a/package.json +++ b/package.json @@ -238,10 +238,6 @@ }, "description": "Folders contains tsserver plugins" }, - "tsserver.debugPort": { - "type": "number", - "description": "Debug port number of tsserver" - }, "tsserver.reportStyleChecksAsWarnings": { "type": "boolean", "default": true diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index 6e99388..e009667 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -25,22 +25,22 @@ import Tracer from './utils/tracer' import { inferredProjectConfig } from './utils/tsconfig' import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider' import VersionStatus from './utils/versionStatus' -import { ICallback, Reader } from './utils/wireProtocol' +import { Reader } from './utils/wireProtocol' interface ToCancelOnResourceChanged { readonly resource: string cancel(): void } -class ForkedTsServerProcess { - constructor(private childProcess: cp.ChildProcess) {} +class ForkedTsServerProcess implements Disposable { + private readonly _reader: Reader + + constructor(private childProcess: cp.ChildProcess) { + this._reader = new Reader(this.childProcess.stdout) + } public readonly toCancelOnResourceChange = new Set() - public onError(cb: (err: Error) => void): void { - this.childProcess.on('error', cb) - } - public onExit(cb: (err: any) => void): void { this.childProcess.on('exit', cb) } @@ -52,16 +52,22 @@ class ForkedTsServerProcess { ) } - public createReader( - callback: ICallback, - onError: (error: any) => void - ): void { - // tslint:disable-next-line:no-unused-expression - new Reader(this.childProcess.stdout, callback, onError) + public onData(handler: (data: Proto.Response) => void): void { + this._reader.onData(handler) + } + + public onError(handler: (err: Error) => void): void { + this.childProcess.on('error', handler) + this._reader.onError(handler) } public kill(): void { this.childProcess.kill() + this._reader.dispose() + } + + public dispose(): void { + this._reader.dispose() } } @@ -296,19 +302,26 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient this.versionStatus.onDidChangeTypeScriptVersion(currentVersion) this.lastError = null const tsServerForkArgs = await this.getTsServerArgs(currentVersion) - const debugPort = this._configuration.debugPort - const maxTsServerMemory = this._configuration.maxTsServerMemory - const options = { - execArgv: [ - ...(debugPort ? [`--inspect=${debugPort}`] : []), // [`--debug-brk=5859`] - ...(maxTsServerMemory ? [`--max-old-space-size=${maxTsServerMemory}`] : []), - ], - cwd: workspace.root - } + const options = { execArgv: this.getExecArgv() } this.servicePromise = this.startProcess(currentVersion, tsServerForkArgs, options, resendModels) return this.servicePromise } + private getExecArgv(): string[] { + const args: string[] = [] + const debugPort = getDebugPort() + if (debugPort) { + const isBreak = process.env[process.env.remoteName ? 'TSS_REMOTE_DEBUG_BRK' : 'TSS_DEBUG_BRK'] !== undefined + const inspectFlag = isBreak ? '--inspect-brk' : '--inspect' + args.push(`${inspectFlag}=${debugPort}`) + } + const maxTsServerMemory = this._configuration.maxTsServerMemory + if (maxTsServerMemory) { + args.push(`--max-old-space-size=${maxTsServerMemory}`) + } + return args + } + private startProcess(currentVersion: TypeScriptVersion, args: string[], options: IForkOptions, resendModels: boolean): Promise { this.state = ServiceStat.Starting return new Promise((resolve, reject) => { @@ -347,16 +360,11 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient this.info(`TSServer log file: ${this.tsServerLogFile || ''}`) this.serviceExited(!this.isRestarting) this.isRestarting = false + handle.dispose() + }) + handle.onData(msg => { + this.dispatchMessage(msg) }) - - handle.createReader( - msg => { - this.dispatchMessage(msg) - }, - error => { - this.error('ReaderError', error) - } - ) resolve(handle) this.serviceStarted(resendModels) this._onTsServerStarted.fire(currentVersion.version) @@ -1013,3 +1021,15 @@ function getQueueingType( } return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal } + +function getDebugPort(): number | undefined { + let debugBrk = process.env[process.env.remoteName ? 'TSS_REMOTE_DEBUG_BRK' : 'TSS_DEBUG_BRK'] + let value = debugBrk || process.env[process.env.remoteName ? 'TSS_REMOTE_DEBUG_BRK' : 'TSS_DEBUG_BRK'] + if (value) { + const port = parseInt(value) + if (!isNaN(port)) { + return port + } + } + return undefined +} diff --git a/src/server/utils/configuration.ts b/src/server/utils/configuration.ts index bac12d9..c320899 100644 --- a/src/server/utils/configuration.ts +++ b/src/server/utils/configuration.ts @@ -108,10 +108,6 @@ export class TypeScriptServiceConfiguration { return this._configuration.get('maxTsServerMemory', 0) } - public get debugPort(): number | null { - return this._configuration.get('debugPort', parseInt(process.env['TSS_DEBUG'], 10)) - } - public get npmLocation(): string | null { let path = this._configuration.get('npm', '') if (path) return workspace.expand(path) diff --git a/src/server/utils/process.ts b/src/server/utils/process.ts index ba2c2bb..c994d17 100644 --- a/src/server/utils/process.ts +++ b/src/server/utils/process.ts @@ -98,16 +98,12 @@ export function fork( ): void { let callbackCalled = false const resolve = (result: cp.ChildProcess) => { - if (callbackCalled) { - return - } + if (callbackCalled) return callbackCalled = true callback(null, result) } const reject = (err: any) => { - if (callbackCalled) { - return - } + if (callbackCalled) return callbackCalled = true callback(err, null) } @@ -123,7 +119,7 @@ export function fork( stdOutPipeName, stdErrPipeName ) - newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..') // tslint:disable-line + newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..') let childProcess: cp.ChildProcess // Begin listening to stderr pipe @@ -165,6 +161,7 @@ export function fork( const bootstrapperPath = path.resolve(__dirname, '../bin/tsserverForkStart') childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), { silent: true, + cwd: undefined, env: newEnv, execArgv: options.execArgv }) diff --git a/src/server/utils/wireProtocol.ts b/src/server/utils/wireProtocol.ts index f71b11f..2b24126 100644 --- a/src/server/utils/wireProtocol.ts +++ b/src/server/utils/wireProtocol.ts @@ -2,7 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { disposeAll } from 'coc.nvim' import stream from 'stream' +import { Disposable, Emitter } from 'vscode-languageserver-protocol' const DefaultSize = 8192 const ContentLength = 'Content-Length: ' @@ -99,18 +101,30 @@ export interface ICallback { (data: T): void // tslint:disable-line } -export class Reader { +export class Reader implements Disposable { private readonly buffer: ProtocolBuffer = new ProtocolBuffer() private nextMessageLength = -1 + private disposables: Disposable[] = [] - public constructor( - private readonly readable: stream.Readable, - private readonly callback: ICallback, - private readonly onError: (error: any) => void - ) { - this.readable.on('data', (data: Buffer) => { + private readonly _onError = new Emitter() + public readonly onError = this._onError.event + + private readonly _onData = new Emitter() + public readonly onData = this._onData.event + + public constructor(readable: stream.Readable) { + const onData = (data: Buffer) => { this.onLengthData(data) + } + readable.on('data', onData) + + this.disposables.push({ + dispose: () => { + readable.off('data', onData) + } }) + this.disposables.push(this._onError) + this.disposables.push(this._onData) } private onLengthData(data: Buffer): void { @@ -129,10 +143,14 @@ export class Reader { } this.nextMessageLength = -1 const json = JSON.parse(msg) - this.callback(json) + this._onData.fire(json) } } catch (e) { - this.onError(e) + this._onError.fire(e) } } + + public dispose(): void { + disposeAll(this.disposables) + } } From 181a337c4a737b961ce914d728a3dc2ef54fddab Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 22 Dec 2021 16:53:53 +0800 Subject: [PATCH 17/78] refactor(server): avoid tsserverForkStart --- bin/tsserverForkStart.js | 161 ------------ src/index.ts | 7 +- src/server/features/bufferSyncSupport.ts | 4 +- src/server/index.ts | 13 +- src/server/languageProvider.ts | 9 +- src/server/tsServerProcess.ts | 55 ++++ src/server/typescriptServiceClient.ts | 294 ++++++++-------------- src/server/typescriptServiceClientHost.ts | 48 ++-- src/server/utils/process.ts | 112 +-------- 9 files changed, 199 insertions(+), 504 deletions(-) delete mode 100644 bin/tsserverForkStart.js create mode 100644 src/server/tsServerProcess.ts diff --git a/bin/tsserverForkStart.js b/bin/tsserverForkStart.js deleted file mode 100644 index 22abfa6..0000000 --- a/bin/tsserverForkStart.js +++ /dev/null @@ -1,161 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -let net = require('net'); -let fs = require('fs'); -let ENABLE_LOGGING = false; -let log = (function () { - if (!ENABLE_LOGGING) { - return function () { }; // tslint:disable-line - } - let isFirst = true; - let LOG_LOCATION = 'C:\\stdFork.log'; - return function log(str) { - if (isFirst) { - isFirst = false; - fs.writeFileSync(LOG_LOCATION, str + '\n'); - return; - } - fs.appendFileSync(LOG_LOCATION, str + '\n'); - }; -})(); -let stdInPipeName = process.env['STDIN_PIPE_NAME']; // tslint:disable-line -let stdOutPipeName = process.env['STDOUT_PIPE_NAME']; // tslint:disable-line -let stdErrPipeName = process.env['STDERR_PIPE_NAME']; // tslint:disable-line -log('STDIN_PIPE_NAME: ' + stdInPipeName); -log('STDOUT_PIPE_NAME: ' + stdOutPipeName); -log('STDERR_PIPE_NAME: ' + stdErrPipeName); -(function () { - log('Beginning stdout redirection...'); - // Create a writing stream to the stdout pipe - let stdOutStream = net.connect(stdOutPipeName); - // unref stdOutStream to behave like a normal standard out - stdOutStream.unref(); - process.__defineGetter__('stdout', function () { - return stdOutStream; - }); - // Create a writing stream to the stderr pipe - let stdErrStream = net.connect(stdErrPipeName); - // unref stdErrStream to behave like a normal standard out - stdErrStream.unref(); - process.__defineGetter__('stderr', function () { - return stdErrStream; - }); - let fsWriteSyncString = function (// tslint:disable-line - fd, str, _position, encoding) { - // fs.writeSync(fd, string[, position[, encoding]]) - let buf = Buffer.from(str, encoding || 'utf8'); - return fsWriteSyncBuffer(fd, buf, 0, buf.length); // tslint:disable-line - }; - let fsWriteSyncBuffer = function (// tslint:disable-line - fd, buffer, off, len) { - off = Math.abs(off | 0); - len = Math.abs(len | 0); - // fs.writeSync(fd, buffer, offset, length[, position]) - let buffer_length = buffer.length; - if (off > buffer_length) { - throw new Error('offset out of bounds'); - } - if (len > buffer_length) { - throw new Error('length out of bounds'); - } - if (((off + len) | 0) < off) { - throw new Error('off + len overflow'); - } - if (buffer_length - off < len) { - // Asking for more than is left over in the buffer - throw new Error('off + len > buffer.length'); - } - let slicedBuffer = buffer; - if (off !== 0 || len !== buffer_length) { - slicedBuffer = buffer.slice(off, off + len); - } - if (fd === 1) { - stdOutStream.write(slicedBuffer); - } - else { - stdErrStream.write(slicedBuffer); - } - return slicedBuffer.length; - }; - // handle fs.writeSync(1, ...) - let originalWriteSync = fs.writeSync; - fs.writeSync = function (// tslint:disable-line - fd, data, _position, _encoding) { - if (fd !== 1 && fd !== 2) { - return originalWriteSync.apply(fs, arguments); - } - // usage: - // fs.writeSync(fd, buffer, offset, length[, position]) - // OR - // fs.writeSync(fd, string[, position[, encoding]]) - if (data instanceof Buffer) { - return fsWriteSyncBuffer.apply(null, arguments); - } - // For compatibility reasons with fs.writeSync, writing null will write "null", etc - if (typeof data !== 'string') { - data += ''; - } - return fsWriteSyncString.apply(null, arguments); - }; - log('Finished defining process.stdout, process.stderr and fs.writeSync'); -})(); -(function () { - // Begin listening to stdin pipe - let server = net.createServer(function (stream) { - // Stop accepting new connections, keep the existing one alive - server.close(); - log('Parent process has connected to my stdin. All should be good now.'); - process.__defineGetter__('stdin', function () { - return stream; - }); - // Remove myself from process.argv - process.argv.splice(1, 1); - // Load the actual program - let program = process.argv[1]; - log('Loading program: ' + program); - // Unset the custom environmental variables that should not get inherited - delete process.env['STDIN_PIPE_NAME']; // tslint:disable-line - delete process.env['STDOUT_PIPE_NAME']; // tslint:disable-line - delete process.env['STDERR_PIPE_NAME']; // tslint:disable-line - require(program); - log('Finished loading program.'); - let stdinIsReferenced = true; - let timer = setInterval(function () { - let listenerCount = stream.listeners('data').length + - stream.listeners('end').length + - stream.listeners('close').length + - stream.listeners('error').length; - // log('listenerCount: ' + listenerCount) - if (listenerCount <= 1) { - // No more "actual" listeners, only internal node - if (stdinIsReferenced) { - stdinIsReferenced = false; - // log('unreferencing stream!!!') - stream.unref(); - } - } - else { - // There are "actual" listeners - if (!stdinIsReferenced) { - stdinIsReferenced = true; - stream.ref(); - } - } - // log( - // '' + stream.listeners('data').length + - // ' ' + stream.listeners('end').length + - // ' ' + stream.listeners('close').length + - // ' ' + stream.listeners('error').length - // ) - }, 1000); - if (timer.unref) { // tslint:disable-line - timer.unref(); // tslint:disable-line - } - }); - server.listen(stdInPipeName, function () { - // signal via stdout that the parent process can now begin writing to stdin pipe - process.stdout.write('ready'); - }); -})(); diff --git a/src/index.ts b/src/index.ts index 18f2845..28b7c42 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,12 +29,7 @@ export async function activate(context: ExtensionContext): Promise { registCommand({ id: 'tsserver.restart', execute: (): void => { - // tslint:disable-next-line:no-floating-promises - service.stop().then(() => { - setTimeout(() => { - service.restart() - }, 100) - }) + service.restart() } }) diff --git a/src/server/features/bufferSyncSupport.ts b/src/server/features/bufferSyncSupport.ts index 627108d..a7ae050 100644 --- a/src/server/features/bufferSyncSupport.ts +++ b/src/server/features/bufferSyncSupport.ts @@ -345,9 +345,7 @@ export default class BufferSyncSupport { } public listen(): void { - if (this.listening) { - return - } + if (this.listening) return this.listening = true workspace.onDidOpenTextDocument( this.openTextDocument, diff --git a/src/server/index.ts b/src/server/index.ts index 92a0b1a..3637093 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -58,18 +58,14 @@ export default class TsserverService implements IServiceProvider { this.disposables.push(this.clientHost) let client = this.clientHost.serviceClient return new Promise(resolve => { - let started = false - client.onTsServerStarted(() => { + client.onReady(() => { Object.defineProperty(this, 'state', { get: () => { return this.clientHost.serviceClient.state } }) this._onDidServiceReady.fire(void 0) - if (!started) { - started = true - resolve() - } + resolve() }) }) } @@ -78,10 +74,10 @@ export default class TsserverService implements IServiceProvider { disposeAll(this.disposables) } - public async restart(): Promise { + public restart(): void { if (!this.clientHost) return let client = this.clientHost.serviceClient - await client.restartTsServer() + client.restartTsServer() } public async stop(): Promise { @@ -89,6 +85,5 @@ export default class TsserverService implements IServiceProvider { this.clientHost.reset() let client = this.clientHost.serviceClient await client.stop() - return } } diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 26368d2..c68d29f 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -49,13 +49,8 @@ export default class LanguageProvider { ) { workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables) this.configurationChanged() - - let initialized = false - client.onTsServerStarted(async () => { // tslint:disable-line - if (!initialized) { - initialized = true - this.registerProviders(client, typingsStatus) - } + client.onReady(() => { + this.registerProviders(client, typingsStatus) }) } diff --git a/src/server/tsServerProcess.ts b/src/server/tsServerProcess.ts new file mode 100644 index 0000000..5877237 --- /dev/null +++ b/src/server/tsServerProcess.ts @@ -0,0 +1,55 @@ + +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import cp from 'child_process' +import { Disposable } from 'vscode-languageserver-protocol' +import * as Proto from './protocol' +import { Reader } from './utils/wireProtocol' + +export interface ToCancelOnResourceChanged { + readonly resource: string + cancel(): void +} + +export default class ForkedTsServerProcess implements Disposable { + private readonly _reader: Reader + + constructor(private childProcess: cp.ChildProcess) { + this._reader = new Reader(this.childProcess.stdout) + } + + public readonly toCancelOnResourceChange = new Set() + + public onExit(cb: (err: any, signal: string) => void): void { + this.childProcess.on('exit', cb) + } + + public write(serverRequest: Proto.Request): void { + this.childProcess.stdin.write( + JSON.stringify(serverRequest) + '\r\n', + 'utf8' + ) + } + + public onData(handler: (data: Proto.Response) => void): void { + this._reader.onData(handler) + } + + public onError(handler: (err: Error) => void): void { + this.childProcess.on('error', handler) + this._reader.onError(handler) + } + + public kill(): void { + this.toCancelOnResourceChange.clear() + this.childProcess.kill() + this._reader.dispose() + } + + public dispose(): void { + this.toCancelOnResourceChange.clear() + this._reader.dispose() + } +} diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index e009667..acaa9ad 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import cp from 'child_process' import { Document, ServiceStat, Uri, window, workspace } from 'coc.nvim' import fs from 'fs' import os from 'os' @@ -25,51 +24,7 @@ import Tracer from './utils/tracer' import { inferredProjectConfig } from './utils/tsconfig' import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider' import VersionStatus from './utils/versionStatus' -import { Reader } from './utils/wireProtocol' - -interface ToCancelOnResourceChanged { - readonly resource: string - cancel(): void -} - -class ForkedTsServerProcess implements Disposable { - private readonly _reader: Reader - - constructor(private childProcess: cp.ChildProcess) { - this._reader = new Reader(this.childProcess.stdout) - } - - public readonly toCancelOnResourceChange = new Set() - - public onExit(cb: (err: any) => void): void { - this.childProcess.on('exit', cb) - } - - public write(serverRequest: Proto.Request): void { - this.childProcess.stdin.write( - JSON.stringify(serverRequest) + '\r\n', - 'utf8' - ) - } - - public onData(handler: (data: Proto.Response) => void): void { - this._reader.onData(handler) - } - - public onError(handler: (err: Error) => void): void { - this.childProcess.on('error', handler) - this._reader.onError(handler) - } - - public kill(): void { - this.childProcess.kill() - this._reader.dispose() - } - - public dispose(): void { - this._reader.dispose() - } -} +import ForkedTsServerProcess, { ToCancelOnResourceChanged } from './tsServerProcess' export interface TsDiagnostics { readonly kind: DiagnosticKind @@ -78,6 +33,7 @@ export interface TsDiagnostics { } export default class TypeScriptServiceClient implements ITypeScriptServiceClient { + private token: number = 0 public state = ServiceStat.Initial public readonly logger: Logger = new Logger() public readonly bufferSyncSupport: BufferSyncSupport @@ -90,14 +46,13 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private versionProvider: TypeScriptVersionProvider private tsServerLogFile: string | null = null private tsServerProcess: ForkedTsServerProcess | undefined - private servicePromise: Thenable | null - private lastError: Error | null private lastStart: number private numberRestarts: number private cancellationPipeName: string | null = null private _callbacks = new CallbackMap() private _requestQueue = new RequestQueue() private _pendingResponses = new Set() + private _onReady?: { promise: Promise; resolve: () => void; reject: () => void } private versionStatus: VersionStatus private readonly _onTsServerStarted = new Emitter() @@ -118,9 +73,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient ) { this.pathSeparator = path.sep this.lastStart = Date.now() - this.servicePromise = null - this.lastError = null this.numberRestarts = 0 + let resolve: () => void + let reject: () => void + const p = new Promise((res, rej) => { + resolve = res + reject = rej + }) + this._onReady = { promise: p, resolve: resolve!, reject: reject! } + this.fileConfigurationManager = new FileConfigurationManager(this) this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace() this.versionProvider = new TypeScriptVersionProvider(this._configuration) @@ -136,7 +97,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient }, null, this.disposables) this.bufferSyncSupport = new BufferSyncSupport(this, modeIds) - this.onTsServerStarted(() => { + this.onReady(() => { this.bufferSyncSupport.listen() }) @@ -169,14 +130,12 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient return this._configuration } + public onReady(f: () => void): Promise { + return this._onReady!.promise.then(f) + } + public dispose(): void { - if (this.servicePromise) { - this.servicePromise - .then(childProcess => { - childProcess.kill() - }) - .then(undefined, () => void 0) - } + this.tsServerProcess.kill() this.bufferSyncSupport.dispose() this.logger.dispose() this._onTsServerStarted.dispose() @@ -192,40 +151,28 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient this.logger.error(message, data) } - public restartTsServer(): Promise { - const start = () => { - this.servicePromise = this.startService(true) - return this.servicePromise - } - - if (this.servicePromise) { - return Promise.resolve(this.servicePromise.then(childProcess => { - this.state = ServiceStat.Stopping - this.info('Killing TS Server') - this.isRestarting = true - childProcess.kill() - this.servicePromise = null - }).then(start)) - } else { - return Promise.resolve(start()) + public restartTsServer(): void { + if (this.tsServerProcess) { + this.state = ServiceStat.Stopping + this.info('Killing TS Server') + this.isRestarting = true + this.tsServerProcess.kill() } + this.startService(true) } public stop(): Promise { - if (!this.servicePromise) return - return new Promise((resolve, reject) => { - this.servicePromise.then(childProcess => { - if (this.state == ServiceStat.Running) { - this.info('Killing TS Server') - childProcess.onExit(() => { - resolve() - }) - childProcess.kill() - this.servicePromise = null - } else { + return new Promise(resolve => { + let { tsServerProcess } = this + if (tsServerProcess && this.state == ServiceStat.Running) { + this.info('Killing TS Server') + tsServerProcess.onExit(() => { resolve() - } - }, reject) + }) + tsServerProcess.kill() + } else { + resolve() + } }) } @@ -259,52 +206,34 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient return this._tscPath } - private service(): Thenable { - if (this.servicePromise) { - return this.servicePromise - } - if (this.lastError) { - return Promise.reject(this.lastError) - } - return this.startService().then(() => { - if (this.servicePromise) { - return this.servicePromise - } - }) - } - public ensureServiceStarted(): void { - if (!this.servicePromise) { - this.startService().catch(err => { - window.showMessage(`TSServer start failed: ${err.message}`, 'error') - this.error(`Service start failed: ${err.stack}`) - }) + if (!this.tsServerProcess) { + this.startService() } } - private async startService(resendModels = false): Promise { + private startService(resendModels = false): ForkedTsServerProcess | undefined { const { ignoreLocalTsserver } = this.configuration let currentVersion: TypeScriptVersion if (!ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion() if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) { + this.info('Local tsserver not found, using bundled tsserver with coc-tsserver.') currentVersion = this.versionProvider.getDefaultVersion() } if (!currentVersion || !currentVersion.isValid) { if (this.configuration.globalTsdk) { - window.showMessage(`Can not find typescript module, in 'tsserver.tsdk': ${this.configuration.globalTsdk}`, 'error') + window.showErrorMessage(`Can not find typescript module, in 'tsserver.tsdk': ${this.configuration.globalTsdk}`) } else { - window.showMessage(`Can not find typescript module, run ':CocInstall coc-tsserver' to fix it!`, 'error') + window.showErrorMessage(`Can not find typescript module, run ':CocInstall coc-tsserver' to fix it!`) } return } this._apiVersion = currentVersion.version this._tscPath = currentVersion.tscPath this.versionStatus.onDidChangeTypeScriptVersion(currentVersion) - this.lastError = null - const tsServerForkArgs = await this.getTsServerArgs(currentVersion) + const tsServerForkArgs = this.getTsServerArgs(currentVersion) const options = { execArgv: this.getExecArgv() } - this.servicePromise = this.startProcess(currentVersion, tsServerForkArgs, options, resendModels) - return this.servicePromise + return this.startProcess(currentVersion, tsServerForkArgs, options, resendModels) } private getExecArgv(): string[] { @@ -322,64 +251,54 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient return args } - private startProcess(currentVersion: TypeScriptVersion, args: string[], options: IForkOptions, resendModels: boolean): Promise { + private startProcess(currentVersion: TypeScriptVersion, args: string[], options: IForkOptions, resendModels: boolean): ForkedTsServerProcess { + const myToken = ++this.token this.state = ServiceStat.Starting - return new Promise((resolve, reject) => { - try { - fork( - currentVersion.tsServerPath, - args, - options, - this.logger, - (err: any, childProcess: cp.ChildProcess | null) => { - if (err || !childProcess) { - this.state = ServiceStat.StartFailed - this.lastError = err - this.error('Starting TSServer failed with error.', err.stack) - return - } - this.state = ServiceStat.Running - this.info('Started TSServer', JSON.stringify(currentVersion, null, 2)) - const handle = new ForkedTsServerProcess(childProcess) - this.tsServerProcess = handle - this.lastStart = Date.now() - - handle.onError((err: Error) => { - this.lastError = err - this.error('TSServer errored with error.', err) - this.error(`TSServer log file: ${this.tsServerLogFile || ''}`) - window.showMessage(`TSServer errored with error. ${err.message}`, 'error') - this.serviceExited(false) - }) - handle.onExit((code: any) => { - if (code == null) { - this.info('TSServer normal exit') - } else { - this.error(`TSServer exited with code: ${code}`) - } - this.info(`TSServer log file: ${this.tsServerLogFile || ''}`) - this.serviceExited(!this.isRestarting) - this.isRestarting = false - handle.dispose() - }) - handle.onData(msg => { - this.dispatchMessage(msg) - }) - resolve(handle) - this.serviceStarted(resendModels) - this._onTsServerStarted.fire(currentVersion.version) - } - ) - } catch (e) { - reject(e) - } - }) + try { + let childProcess = fork(currentVersion.tsServerPath, args, options, this.logger) + this.state = ServiceStat.Running + this.info('Starting TSServer', JSON.stringify(currentVersion, null, 2)) + const handle = new ForkedTsServerProcess(childProcess) + this.tsServerProcess = handle + this.lastStart = Date.now() + handle.onError((err: Error) => { + if (this.token != myToken) return + window.showErrorMessage(`TypeScript language server exited with error. Error message is: ${err.message}`) + this.error('TSServer errored with error.', err) + this.error(`TSServer log file: ${this.tsServerLogFile || ''}`) + window.showMessage(`TSServer errored with error. ${err.message}`, 'error') + this.serviceExited(false) + }) + handle.onExit((code: any, signal: string) => { + handle.dispose() + if (this.token != myToken) return + if (code == null) { + this.info(`TSServer exited. Signal: ${signal}`) + } else { + this.error(`TSServer exited with code: ${code}. Signal: ${signal}`) + } + this.info(`TSServer log file: ${this.tsServerLogFile || ''}`) + this.serviceExited(!this.isRestarting) + this.isRestarting = false + }) + handle.onData(msg => { + this.dispatchMessage(msg) + }) + this.serviceStarted(resendModels) + this._onReady!.resolve() + this._onTsServerStarted.fire(currentVersion.version) + return handle + } catch (err) { + this.state = ServiceStat.StartFailed + this.error('Starting TSServer failed with error.', err.stack) + return undefined + } } public async openTsServerLogFile(): Promise { const isRoot = process.getuid && process.getuid() == 0 let echoErr = (msg: string) => { - window.showMessage(msg, 'error') + window.showErrorMessage(msg) } if (isRoot) { echoErr('Log disabled for root user.') @@ -457,7 +376,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private serviceExited(restart: boolean): void { this.state = ServiceStat.Stopped - this.servicePromise = null this.tsServerLogFile = null this._callbacks.destroy('Service died.') this._callbacks = new CallbackMap() @@ -479,7 +397,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } } if (startService) { - this.startService(true) // tslint:disable-line + this.startService(true) } } } @@ -491,7 +409,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient public toOpenedFilePath(uri: string, options: { suppressAlertOnFailure?: boolean } = {}): string | undefined { if (!this.bufferSyncSupport.ensureHasBuffer(uri)) { if (!options.suppressAlertOnFailure) { - console.error(`Unexpected resource ${uri}`) + this.error(`Unexpected resource ${uri}`) } return undefined } @@ -608,13 +526,14 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } private fatalError(command: string, error: any): void { - console.error(`A non-recoverable error occured while executing tsserver command: ${command}`) - + this.error(`A non-recoverable error occured while executing tsserver command: ${command}`) if (this.state === ServiceStat.Running) { this.info('Killing TS Server by fatal error:', error) - this.service().then(service => { - service.kill() - }) + let { tsServerProcess } = this + if (tsServerProcess) { + this.tsServerProcess = undefined + tsServerProcess.kill() + } } } @@ -639,7 +558,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise> | undefined { - if (this.servicePromise == null) { + if (!this.tsServerProcess) { return Promise.resolve(undefined) } this.bufferSyncSupport.beforeCommand(command) @@ -687,16 +606,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient if (requestItem.expectsResponse && !requestItem.isAsync) { this._pendingResponses.add(requestItem.request.seq) } - this.service().then(childProcess => { - try { - childProcess.write(serverRequest) - } catch (err) { - const callback = this.fetchCallback(serverRequest.seq) - if (callback) { - callback.onError(err) - } + if (!this.tsServerProcess) return + try { + this.tsServerProcess.write(serverRequest) + } catch (err) { + const callback = this.fetchCallback(serverRequest.seq) + if (callback) { + callback.onError(err) } - }) + } } private tryCancelRequest(seq: number, command: string): boolean { @@ -731,7 +649,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient if (!callback) { return undefined } - this._pendingResponses.delete(seq) return callback } @@ -849,7 +766,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } } - private async getTsServerArgs(currentVersion: TypeScriptVersion): Promise { + private getTsServerArgs(currentVersion: TypeScriptVersion): string[] { const args: string[] = [] args.push('--allowLocalPluginLoads') @@ -974,11 +891,8 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient public configurePlugin(pluginName: string, configuration: {}): any { if (this.apiVersion.gte(API.v314)) { - if (!this.servicePromise) return - this.servicePromise.then(() => { - // tslint:disable-next-line: no-floating-promises - this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration }) - }) + if (!this.tsServerProcess) return + this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration }) } } diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index dea0311..0305b25 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -102,39 +102,41 @@ export default class TypeScriptServiceClientHost implements Disposable { ) this.languagePerId.set(description.id, manager) } - const languageIds = new Set() - for (const plugin of pluginManager.plugins) { - if (plugin.configNamespace && plugin.languages.length) { + this.client.ensureServiceStarted() + this.client.onReady(() => { + const languageIds = new Set() + for (const plugin of pluginManager.plugins) { + if (plugin.configNamespace && plugin.languages.length) { + this.registerExtensionLanguageProvider({ + id: plugin.configNamespace, + modeIds: Array.from(plugin.languages), + diagnosticSource: 'ts-plugin', + diagnosticLanguage: DiagnosticLanguage.TypeScript, + diagnosticOwner: 'typescript', + isExternal: true + }) + } else { + for (const language of plugin.languages) { + languageIds.add(language) + } + } + } + + if (languageIds.size) { this.registerExtensionLanguageProvider({ - id: plugin.configNamespace, - modeIds: Array.from(plugin.languages), + id: 'typescript-plugins', + modeIds: Array.from(languageIds.values()), diagnosticSource: 'ts-plugin', diagnosticLanguage: DiagnosticLanguage.TypeScript, diagnosticOwner: 'typescript', isExternal: true }) - } else { - for (const language of plugin.languages) { - languageIds.add(language) - } } - } - - if (languageIds.size) { - this.registerExtensionLanguageProvider({ - id: 'typescript-plugins', - modeIds: Array.from(languageIds.values()), - diagnosticSource: 'ts-plugin', - diagnosticLanguage: DiagnosticLanguage.TypeScript, - diagnosticOwner: 'typescript', - isExternal: true - }) - } - - this.client.ensureServiceStarted() + }) this.client.onTsServerStarted(() => { this.triggerAllDiagnostics() }) + workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables) this.configurationChanged() } diff --git a/src/server/utils/process.ts b/src/server/utils/process.ts index c994d17..8580943 100644 --- a/src/server/utils/process.ts +++ b/src/server/utils/process.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import cp from 'child_process' -import net from 'net' import os from 'os' import path from 'path' import fs from 'fs' @@ -36,21 +35,6 @@ export function getTempDirectory(): string | undefined { return dir } -function generatePipeName(): string { - return getPipeName(makeRandomHexString(40)) -} - -function getPipeName(name: string): string | undefined { - const fullName = 'coc-tsc-' + name - if (process.platform === 'win32') { - return '\\\\.\\pipe\\' + fullName + '-sock' - } - const tmpdir = getTempDirectory() - if (!tmpdir) return undefined - // Mac/Unix: use socket file - return path.join(tmpdir, fullName + '.sock') -} - export function getTempFile(name: string): string | undefined { const fullName = 'coc-nvim-' + name let dir = getTempDirectory() @@ -70,20 +54,9 @@ export function createTempDirectory(name: string) { return res } -function generatePatchedEnv( - env: any, - stdInPipeName: string, - stdOutPipeName: string, - stdErrPipeName: string -): any { +function generatePatchedEnv(env: any, modulePath: string): any { const newEnv = Object.assign({}, env) - - // Set the two unique pipe names and the electron flag as process env - newEnv['STDIN_PIPE_NAME'] = stdInPipeName // tslint:disable-line - newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName // tslint:disable-line - newEnv['STDERR_PIPE_NAME'] = stdErrPipeName // tslint:disable-line - newEnv['TSS_LOG'] = `-level verbose -file ${path.join(os.tmpdir(), 'coc-nvim-tsc.log')}` // tslint:disable-line - + newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..') // Ensure we always have a PATH set newEnv['PATH'] = newEnv['PATH'] || process.env.PATH // tslint:disable-line return newEnv @@ -94,85 +67,14 @@ export function fork( args: string[], options: IForkOptions, logger: Logger, - callback: (error: any, cp: cp.ChildProcess | null) => void -): void { - let callbackCalled = false - const resolve = (result: cp.ChildProcess) => { - if (callbackCalled) return - callbackCalled = true - callback(null, result) - } - const reject = (err: any) => { - if (callbackCalled) return - callbackCalled = true - callback(err, null) - } - - // Generate three unique pipe names - const stdInPipeName = generatePipeName() - const stdOutPipeName = generatePipeName() - const stdErrPipeName = generatePipeName() - - const newEnv = generatePatchedEnv( - process.env, - stdInPipeName, - stdOutPipeName, - stdErrPipeName - ) - newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..') - - let childProcess: cp.ChildProcess - // Begin listening to stderr pipe - let stdErrServer = net.createServer(stdErrStream => { - // From now on the childProcess.stderr is available for reading - childProcess.stderr = stdErrStream - }) - stdErrServer.listen(stdErrPipeName) - - // Begin listening to stdout pipe - let stdOutServer = net.createServer(stdOutStream => { - // The child process will write exactly one chunk with content `ready` when it has installed a listener to the stdin pipe - - stdOutStream.once('data', (_chunk: Buffer) => { - // The child process is sending me the `ready` chunk, time to connect to the stdin pipe - childProcess.stdin = net.connect(stdInPipeName) as any - - // From now on the childProcess.stdout is available for reading - childProcess.stdout = stdOutStream - - resolve(childProcess) - }) - }) - stdOutServer.listen(stdOutPipeName) - - let serverClosed = false - const closeServer = () => { - if (serverClosed) { - return - } - serverClosed = true - stdOutServer.close() - stdErrServer.close() - } - +): cp.ChildProcess { // Create the process - logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `) - - const bootstrapperPath = path.resolve(__dirname, '../bin/tsserverForkStart') - childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), { + logger.info('Forking TSServer', `PATH: ${modulePath} `) + let childProcess = cp.fork(modulePath, args, { silent: true, cwd: undefined, - env: newEnv, + env: generatePatchedEnv(process.env, modulePath), execArgv: options.execArgv }) - - childProcess.once('error', (err: Error) => { - closeServer() - reject(err) - }) - - childProcess.once('exit', (err: Error) => { - closeServer() - reject(err) - }) + return childProcess } From 7b363e237e2e4c4379fc7f61362aa69dbe88c0ee Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 22 Dec 2021 17:01:14 +0800 Subject: [PATCH 18/78] Release 1.9.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8a27220..0e8e29e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.0", + "version": "1.9.1", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From de681378504eccee497bfacb9af85ff758e9b734 Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Fri, 24 Dec 2021 18:29:47 +0800 Subject: [PATCH 19/78] feat: inlay hints support (#335) close #300 --- package.json | 106 +++++++++++- .../features/fileConfigurationManager.ts | 32 ++++ src/server/features/inlayHints.ts | 160 ++++++++++++++++++ src/server/languageProvider.ts | 4 + src/server/typescriptService.ts | 1 + src/server/utils/api.ts | 1 + 6 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 src/server/features/inlayHints.ts diff --git a/package.json b/package.json index 0e8e29e..6369768 100644 --- a/package.json +++ b/package.json @@ -730,7 +730,111 @@ "javascript.suggest.includeAutomaticOptionalChainCompletions": { "type": "boolean", "default": true, - "description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%", + "description": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires TS 3.7+ and strict null checks to be enabled.", + "scope": "resource" + }, + "typescript.inlayHints.parameterNames.enabled": { + "type": "string", + "enum": [ + "none", + "literals", + "all" + ], + "enumDescriptions": [ + "Disable parameter name hints.", + "Enable parameter name hints only for literal arguments.", + "Enable parameter name hints for literal and non-literal arguments." + ], + "default": "none", + "description": "Enable/disable inlay hints of parameter names.", + "scope": "resource" + }, + "typescript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": { + "type": "boolean", + "default": true, + "description": "Suppress parameter name hints on arguments whose text is identical to the parameter name.", + "scope": "resource" + }, + "typescript.inlayHints.parameterTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of parameter types.", + "scope": "resource" + }, + "typescript.inlayHints.variableTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of variable types.", + "scope": "resource" + }, + "typescript.inlayHints.propertyDeclarationTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of property declarations.", + "scope": "resource" + }, + "typescript.inlayHints.functionLikeReturnTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of return type for function signatures.", + "scope": "resource" + }, + "typescript.inlayHints.enumMemberValues.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of enum member values.", + "scope": "resource" + }, + "javascript.inlayHints.parameterNames.enabled": { + "type": "string", + "enum": [ + "none", + "literals", + "all" + ], + "enumDescriptions": [ + "Disable parameter name hints.", + "Enable parameter name hints only for literal arguments.", + "Enable parameter name hints for literal and non-literal arguments." + ], + "default": "none", + "description": "Enable/disable inlay hints of parameter names.", + "scope": "resource" + }, + "javascript.inlayHints.parameterNames.suppressWhenArgumentMatchesName": { + "type": "boolean", + "default": true, + "description": "Suppress parameter name hints on arguments whose text is identical to the parameter name.", + "scope": "resource" + }, + "javascript.inlayHints.parameterTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of parameter types.", + "scope": "resource" + }, + "javascript.inlayHints.variableTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of variable types.", + "scope": "resource" + }, + "javascript.inlayHints.propertyDeclarationTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of property declarations.", + "scope": "resource" + }, + "javascript.inlayHints.functionLikeReturnTypes.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of return type for function signatures.", + "scope": "resource" + }, + "javascript.inlayHints.enumMemberValues.enabled": { + "type": "boolean", + "default": false, + "description": "Enable/disable inlay hints of enum member values.", "scope": "resource" }, "javascript.autoClosingTags": { diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 2b8141a..1c5b94d 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -193,6 +193,7 @@ export default class FileConfigurationManager { includeCompletionsWithSnippetText: suggestConfig.includeCompletionsWithSnippetText, allowIncompleteCompletions: true, displayPartsForJSDoc: true, + ...getInlayHintsPreferences(language), } return preferences } @@ -240,3 +241,34 @@ function getJsxAttributeCompletionStyle(config: WorkspaceConfiguration) { default: return 'auto' } } + +export class InlayHintSettingNames { + static readonly parameterNamesSuppressWhenArgumentMatchesName = 'inlayHints.parameterNames.suppressWhenArgumentMatchesName' + static readonly parameterNamesEnabled = 'inlayHints.parameterTypes.enabled' + static readonly variableTypesEnabled = 'inlayHints.variableTypes.enabled' + static readonly propertyDeclarationTypesEnabled = 'inlayHints.propertyDeclarationTypes.enabled' + static readonly functionLikeReturnTypesEnabled = 'inlayHints.functionLikeReturnTypes.enabled' + static readonly enumMemberValuesEnabled = 'inlayHints.enumMemberValues.enabled' +} + +export function getInlayHintsPreferences(language: string) { + const config = workspace.getConfiguration(language) + return { + includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config), + includeInlayParameterNameHintsWhenArgumentMatchesName: !config.get(InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName, true), + includeInlayFunctionParameterTypeHints: config.get(InlayHintSettingNames.parameterNamesEnabled, false), + includeInlayVariableTypeHints: config.get(InlayHintSettingNames.variableTypesEnabled, false), + includeInlayPropertyDeclarationTypeHints: config.get(InlayHintSettingNames.propertyDeclarationTypesEnabled, false), + includeInlayFunctionLikeReturnTypeHints: config.get(InlayHintSettingNames.functionLikeReturnTypesEnabled, false), + includeInlayEnumMemberValueHints: config.get(InlayHintSettingNames.enumMemberValuesEnabled, false), + } as const +} + +function getInlayParameterNameHintsPreference(config: WorkspaceConfiguration) { + switch (config.get('inlayHints.parameterNames.enabled')) { + case 'none': return 'none' + case 'literals': return 'literals' + case 'all': return 'all' + default: return undefined + } +} diff --git a/src/server/features/inlayHints.ts b/src/server/features/inlayHints.ts new file mode 100644 index 0000000..c018a89 --- /dev/null +++ b/src/server/features/inlayHints.ts @@ -0,0 +1,160 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. +*--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, CancellationTokenSource, Disposable, disposeAll, Document, events, Position, Range, TextDocument, workspace } from 'coc.nvim' +import type * as Proto from '../protocol' +import { ITypeScriptServiceClient } from '../typescriptService' +import API from '../utils/api' +import FileConfigurationManager, { getInlayHintsPreferences } from './fileConfigurationManager' + +export enum InlayHintKind { + Other = 0, + Type = 1, + Parameter = 2 +} + +export interface InlayHint { + text: string + position: Position + kind: InlayHintKind + whitespaceBefore?: boolean + whitespaceAfter?: boolean +} + +export default class TypeScriptInlayHintsProvider implements Disposable { + public static readonly minVersion = API.v440 + private readonly inlayHintsNS = workspace.createNameSpace('tsserver-inlay-hint') + + private _disposables: Disposable[] = [] + private _tokenSource: CancellationTokenSource | undefined = undefined + private _inlayHints: Map = new Map() + + public dispose() { + if (this._tokenSource) { + this._tokenSource.cancel() + this._tokenSource.dispose() + this._tokenSource = undefined + } + + disposeAll(this._disposables) + this._disposables = [] + this._inlayHints.clear() + } + + constructor(private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager) { + events.on('InsertLeave', async bufnr => { + const doc = workspace.getDocument(bufnr) + await this.syncAndRenderHints(doc) + }, this, this._disposables) + + workspace.onDidOpenTextDocument(async e => { + const doc = workspace.getDocument(e.bufnr) + await this.syncAndRenderHints(doc) + }, this, this._disposables) + + workspace.onDidChangeTextDocument(async e => { + const doc = workspace.getDocument(e.bufnr) + await this.syncAndRenderHints(doc) + }, this, this._disposables) + + this.syncAndRenderHints() + } + + private async syncAndRenderHints(doc?: Document) { + if (!doc) doc = await workspace.document + if (!isESDocument(doc)) return + + if (this._tokenSource) { + this._tokenSource.cancel() + this._tokenSource.dispose() + } + + try { + this._tokenSource = new CancellationTokenSource() + const { token } = this._tokenSource + const range = Range.create(0, 0, doc.lineCount, doc.getline(doc.lineCount).length) + const hints = await this.provideInlayHints(doc.textDocument, range, token) + if (token.isCancellationRequested) return + + await this.renderHints(doc, hints) + } catch (e) { + console.error(e) + this._tokenSource.cancel() + this._tokenSource.dispose() + } + } + + private async renderHints(doc: Document, hints: InlayHint[]) { + this._inlayHints.set(doc.uri, hints) + + const chaining_hints = {} + for (const item of hints) { + const chunks: [[string, string]] = [[item.text, 'CocHintSign']] + if (chaining_hints[item.position.line] === undefined) { + chaining_hints[item.position.line] = chunks + } else { + chaining_hints[item.position.line].push([' ', 'Normal']) + chaining_hints[item.position.line].push(chunks[0]) + } + } + + doc.buffer.clearNamespace(this.inlayHintsNS) + Object.keys(chaining_hints).forEach(async (line) => { + await doc.buffer.setVirtualText(this.inlayHintsNS, Number(line), chaining_hints[line], {}) + }) + } + + private inlayHintsEnabled(language: string) { + const preferences = getInlayHintsPreferences(language) + return preferences.includeInlayParameterNameHints === 'literals' + || preferences.includeInlayParameterNameHints === 'all' + || preferences.includeInlayEnumMemberValueHints + || preferences.includeInlayFunctionLikeReturnTypeHints + || preferences.includeInlayFunctionParameterTypeHints + || preferences.includeInlayPropertyDeclarationTypeHints + || preferences.includeInlayVariableTypeHints + } + + async provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise { + if (!this.inlayHintsEnabled(document.languageId)) return [] + + const filepath = this.client.toOpenedFilePath(document.uri) + if (!filepath) return [] + + const start = document.offsetAt(range.start) + const length = document.offsetAt(range.end) - start + + await this.fileConfigurationManager.ensureConfigurationForDocument(document, token) + + const response = await this.client.execute('provideInlayHints', { file: filepath, start, length }, token) + if (response.type !== 'response' || !response.success || !response.body) { + return [] + } + + return response.body.map(hint => { + return { + text: hint.text, + position: Position.create(hint.position.line - 1, hint.position.offset - 1), + kind: hint.kind && fromProtocolInlayHintKind(hint.kind), + whitespaceAfter: hint.whitespaceAfter, + whitespaceBefore: hint.whitespaceBefore, + } + }) + } +} + +function isESDocument(doc: Document) { + if (!doc || !doc.attached) return false + return doc.filetype === 'typescript' || doc.filetype === 'javascript' +} + +function fromProtocolInlayHintKind(kind: Proto.InlayHintKind): InlayHintKind { + switch (kind) { + case 'Parameter': return InlayHintKind.Parameter + case 'Type': return InlayHintKind.Type + case 'Enum': return InlayHintKind.Other + default: return InlayHintKind.Other + } +} diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index c68d29f..9330fe0 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -29,6 +29,7 @@ import SignatureHelpProvider from './features/signatureHelp' import SemanticTokensProvider from './features/semanticTokens' import SmartSelection from './features/smartSelect' import TagClosing from './features/tagClosing' +import TypeScriptInlayHintsProvider from './features/inlayHints' import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' import { OrganizeImportsCodeActionProvider } from './organizeImports' import TypeScriptServiceClient from './typescriptServiceClient' @@ -159,6 +160,9 @@ export default class LanguageProvider { if (this.client.apiVersion.gte(API.v300)) { this._register(new TagClosing(this.client, this.description.id)) } + if (this.client.apiVersion.gte(API.v440) && workspace.isNvim) { + this._register(new TypeScriptInlayHintsProvider(this.client, this.fileConfigurationManager)) + } } public handles(resource: string, doc: TextDocument): boolean { diff --git a/src/server/typescriptService.ts b/src/server/typescriptService.ts index 2b6f7b6..88a097b 100644 --- a/src/server/typescriptService.ts +++ b/src/server/typescriptService.ts @@ -84,6 +84,7 @@ export interface TypeScriptRequestTypes { 'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse] 'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse] 'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse] + 'provideInlayHints': [Proto.InlayHintsRequestArgs, Proto.InlayHintsResponse] } export interface ITypeScriptServiceClient { diff --git a/src/server/utils/api.ts b/src/server/utils/api.ts index 9e0dc5a..0dce70a 100644 --- a/src/server/utils/api.ts +++ b/src/server/utils/api.ts @@ -43,6 +43,7 @@ export default class API { public static readonly v401 = API.fromSimpleString('4.0.1') public static readonly v420 = API.fromSimpleString('4.2.0') public static readonly v430 = API.fromSimpleString('4.3.0') + public static readonly v440 = API.fromSimpleString('4.4.0') public static fromVersionString(versionString: string): API { let version = semver.valid(versionString) From 867b7668623ed219e63b5d04589bdf618dcb15dd Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 24 Dec 2021 18:39:29 +0800 Subject: [PATCH 20/78] doc --- Readme.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Readme.md b/Readme.md index 6c1e422..ce776d5 100644 --- a/Readme.md +++ b/Readme.md @@ -92,6 +92,11 @@ Almost the same as VSCode. - Rename imports on file rename, require [watchman](https://facebook.github.io/watchman/) installed in your \$PATH. - Search for workspace symbols. +- Inlay hints support using virtual text feature of neovim, which requires: + - TypeScript >= 4.4.0 + - Neovim >= 0.4.0 + - Enabled by options starts with `typescript.inlayHints` or + `javascript.inlayHints`. Tsserver module first resolved from your local workspace. If it's not found, use tsserver from `tsserver.tsdk` configuration or use bundled tsserver with this @@ -197,6 +202,7 @@ for guide of coc.nvim's configuration. - `typescript.format.placeOpenBraceOnNewLineForFunctions` default: `false` - `typescript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false` - `typescript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` +- `typescript.inlayHints`: inlayHints related options. - `javascript.format.enabled`: Enable/disable format for javascript files, default: `true` - `javascript.showUnused`: show unused variable hint, default: `true` - `javascript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false` @@ -253,6 +259,7 @@ for guide of coc.nvim's configuration. - `javascript.format.placeOpenBraceOnNewLineForFunctions` default: `false` - `javascript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false` - `javascript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` +- `javascript.inlayHints`: inlayHints related options. Configurations are the same as with VSCode. Try completion with `tsserver`, `typescript` or `javascript` in your `coc-settings.json`. From 4a146e3d8dcf864b2213dd3ac561abe77424655a Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 24 Dec 2021 18:45:45 +0800 Subject: [PATCH 21/78] Release 1.9.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6369768..cdebf99 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.1", + "version": "1.9.2", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From c42ed843b0b172b6082d4ec75366c9d969ba9f64 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 25 Dec 2021 00:29:52 +0800 Subject: [PATCH 22/78] fixx issues with inlayHints Closes #336 --- src/server/features/inlayHints.ts | 55 +++++++++++++++++++------------ src/server/languageProvider.ts | 2 +- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/src/server/features/inlayHints.ts b/src/server/features/inlayHints.ts index c018a89..30ad69a 100644 --- a/src/server/features/inlayHints.ts +++ b/src/server/features/inlayHints.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource, Disposable, disposeAll, Document, events, Position, Range, TextDocument, workspace } from 'coc.nvim' +import { CancellationToken, CancellationTokenSource, Disposable, disposeAll, Document, Position, Range, TextDocument, workspace } from 'coc.nvim' import type * as Proto from '../protocol' import { ITypeScriptServiceClient } from '../typescriptService' import API from '../utils/api' @@ -43,28 +43,48 @@ export default class TypeScriptInlayHintsProvider implements Disposable { this._inlayHints.clear() } - constructor(private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager) { - events.on('InsertLeave', async bufnr => { - const doc = workspace.getDocument(bufnr) - await this.syncAndRenderHints(doc) - }, this, this._disposables) - + constructor( + private readonly client: ITypeScriptServiceClient, + private readonly fileConfigurationManager: FileConfigurationManager, + private readonly languageIds: string[] + ) { + let languageId = this.languageIds[0] + let section = `${languageId}.inlayHints` + workspace.onDidChangeConfiguration(async e => { + if (e.affectsConfiguration(section)) { + for (let doc of workspace.documents) { + if (!this.inlayHintsEnabled(languageId)) { + doc.buffer.clearNamespace(this.inlayHintsNS) + } else { + await this.syncAndRenderHints(doc) + } + } + } + }, null, this._disposables) workspace.onDidOpenTextDocument(async e => { const doc = workspace.getDocument(e.bufnr) await this.syncAndRenderHints(doc) - }, this, this._disposables) + }, null, this._disposables) workspace.onDidChangeTextDocument(async e => { const doc = workspace.getDocument(e.bufnr) - await this.syncAndRenderHints(doc) - }, this, this._disposables) + if (this.languageIds.includes(doc.textDocument.languageId)) { + this.renderHintsForAllDocuments() + } + }, null, this._disposables) - this.syncAndRenderHints() + this.renderHintsForAllDocuments() } - private async syncAndRenderHints(doc?: Document) { - if (!doc) doc = await workspace.document - if (!isESDocument(doc)) return + private async renderHintsForAllDocuments(): Promise { + for (let doc of workspace.documents) { + await this.syncAndRenderHints(doc) + } + } + + private async syncAndRenderHints(doc: Document) { + if (!this.languageIds.includes(doc.textDocument.languageId)) return + if (!this.inlayHintsEnabled(this.languageIds[0])) return if (this._tokenSource) { this._tokenSource.cancel() @@ -118,8 +138,6 @@ export default class TypeScriptInlayHintsProvider implements Disposable { } async provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise { - if (!this.inlayHintsEnabled(document.languageId)) return [] - const filepath = this.client.toOpenedFilePath(document.uri) if (!filepath) return [] @@ -145,11 +163,6 @@ export default class TypeScriptInlayHintsProvider implements Disposable { } } -function isESDocument(doc: Document) { - if (!doc || !doc.attached) return false - return doc.filetype === 'typescript' || doc.filetype === 'javascript' -} - function fromProtocolInlayHintKind(kind: Proto.InlayHintKind): InlayHintKind { switch (kind) { case 'Parameter': return InlayHintKind.Parameter diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 9330fe0..6667608 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -161,7 +161,7 @@ export default class LanguageProvider { this._register(new TagClosing(this.client, this.description.id)) } if (this.client.apiVersion.gte(API.v440) && workspace.isNvim) { - this._register(new TypeScriptInlayHintsProvider(this.client, this.fileConfigurationManager)) + this._register(new TypeScriptInlayHintsProvider(this.client, this.fileConfigurationManager, languageIds)) } } From ca715ce092e155cea441366817c4417b10c34a4f Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 25 Dec 2021 00:30:21 +0800 Subject: [PATCH 23/78] Release 1.9.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cdebf99..ee8dc8a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.2", + "version": "1.9.3", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 8f96c72ad3ba1af94324de58e18db926c1e0db2b Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 25 Dec 2021 18:17:07 +0800 Subject: [PATCH 24/78] fix diagnostics not works with unopened document --- src/server/languageProvider.ts | 13 +++++++++++-- src/server/typescriptServiceClientHost.ts | 17 +++++++++++------ src/server/utils/languageDescription.ts | 19 +++++++++++++++++-- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 6667608..58a431d 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -166,13 +166,22 @@ export default class LanguageProvider { } public handles(resource: string, doc: TextDocument): boolean { - if (doc && this.description.modeIds.indexOf(doc.languageId) >= 0) { + if (doc && this.description.modeIds.includes(doc.languageId)) { return true } - const base = path.basename(Uri.parse(resource).fsPath) + return this.handlesConfigFile(Uri.parse(resource)) + } + + private handlesConfigFile(uri: Uri): boolean { + const base = path.basename(uri.fsPath) return !!base && (!!this.description.configFilePattern && this.description.configFilePattern.test(base)) } + public handlesUri(resource: Uri): boolean { + const ext = path.extname(resource.path).slice(1).toLowerCase() + return this.description.standardFileExtensions.includes(ext) || this.handlesConfigFile(resource) + } + private get id(): string { // tslint:disable-line return this.description.id } diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index 0305b25..9b575fe 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { disposeAll, languages, TextDocument, Uri, workspace } from 'coc.nvim' +import { ConfigurationChangeEvent, disposeAll, languages, TextDocument, Uri, workspace } from 'coc.nvim' import { CancellationToken, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Disposable, Position, Range } from 'vscode-languageserver-protocol' import { flatten } from '../utils/arrays' import { PluginManager } from '../utils/plugins' @@ -113,6 +113,7 @@ export default class TypeScriptServiceClientHost implements Disposable { diagnosticSource: 'ts-plugin', diagnosticLanguage: DiagnosticLanguage.TypeScript, diagnosticOwner: 'typescript', + standardFileExtensions: [], isExternal: true }) } else { @@ -129,6 +130,7 @@ export default class TypeScriptServiceClientHost implements Disposable { diagnosticSource: 'ts-plugin', diagnosticLanguage: DiagnosticLanguage.TypeScript, diagnosticOwner: 'typescript', + standardFileExtensions: [], isExternal: true }) } @@ -176,17 +178,20 @@ export default class TypeScriptServiceClientHost implements Disposable { return this.languagePerId.get(languageId) } - private configurationChanged(): void { - const config = workspace.getConfiguration('tsserver') - this.reportStyleCheckAsWarnings = config.get('reportStyleChecksAsWarnings', true) + private configurationChanged(e?: ConfigurationChangeEvent): void { + if (!e || e.affectsConfiguration('tsserver')) { + const config = workspace.getConfiguration('tsserver') + this.reportStyleCheckAsWarnings = config.get('reportStyleChecksAsWarnings', true) + } } public async findLanguage(uri: string): Promise { try { let doc = this.client.getDocument(uri) - if (!doc) return undefined let languages = Array.from(this.languagePerId.values()) - return languages.find(language => language.handles(uri, doc.textDocument)) + // possible not opened + if (doc) return languages.find(language => language.handles(uri, doc.textDocument)) + return languages.find(language => language.handlesUri(Uri.parse(uri))) } catch { return undefined } diff --git a/src/server/utils/languageDescription.ts b/src/server/utils/languageDescription.ts index 75cd1f9..c3a09af 100644 --- a/src/server/utils/languageDescription.ts +++ b/src/server/utils/languageDescription.ts @@ -13,6 +13,7 @@ export interface LanguageDescription { readonly isExternal?: boolean readonly diagnosticOwner: string readonly configFilePattern?: RegExp + readonly standardFileExtensions: ReadonlyArray, } export const enum DiagnosticLanguage { @@ -29,7 +30,13 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ languageModeIds.typescripttsx, languageModeIds.typescriptjsx], diagnosticLanguage: DiagnosticLanguage.TypeScript, configFile: 'tsconfig.json', - configFilePattern: /^tsconfig(\..*)?\.json$/gi + configFilePattern: /^tsconfig(\..*)?\.json$/gi, + standardFileExtensions: [ + 'ts', + 'tsx', + 'cts', + 'mts' + ] }, { id: 'javascript', @@ -38,6 +45,14 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact, languageModeIds.javascriptjsx], diagnosticLanguage: DiagnosticLanguage.JavaScript, configFile: 'jsconfig.json', - configFilePattern: /^jsconfig(\..*)?\.json$/gi + configFilePattern: /^jsconfig(\..*)?\.json$/gi, + standardFileExtensions: [ + 'js', + 'jsx', + 'cjs', + 'mjs', + 'es6', + 'pac', + ] } ] From cd7c05d4a2d660995b942072f13633b51d1505d3 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 25 Dec 2021 18:33:00 +0800 Subject: [PATCH 25/78] remove configFile from LanguageDescription --- src/server/features/watchBuild.ts | 18 ++--------------- src/server/languageProvider.ts | 4 ++-- src/server/utils/languageDescription.ts | 27 ++++++++++++++++++------- tsconfig.json | 1 + 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/server/features/watchBuild.ts b/src/server/features/watchBuild.ts index ea40c7b..7ea12c3 100644 --- a/src/server/features/watchBuild.ts +++ b/src/server/features/watchBuild.ts @@ -1,24 +1,10 @@ -import { commands, disposeAll, StatusBarItem, TaskOptions, Uri, window, workspace } from 'coc.nvim' +import { commands, Disposable, disposeAll, StatusBarItem, TaskOptions, Uri, window, workspace } from 'coc.nvim' import path from 'path' -import { Disposable, Location } from 'vscode-languageserver-protocol' import TypeScriptServiceClient from '../typescriptServiceClient' const countRegex = /Found\s+(\d+)\s+error/ const errorRegex = /^(.+)\((\d+),(\d+)\):\s(\w+)\sTS(\d+):\s*(.+)$/ -interface ErrorItem { - location: Location - text: string - type: string -} - -enum TscStatus { - INIT, - COMPILING, - RUNNING, - ERROR, -} - export default class WatchProject implements Disposable { private disposables: Disposable[] = [] public static readonly id: string = 'tsserver.watchBuild' @@ -119,7 +105,7 @@ export default class WatchProject implements Disposable { return } - const tsconfigPath = workspace.getConfiguration('tsserver').get('tsconfigPath', 'tsconfig.json'); + const tsconfigPath = workspace.getConfiguration('tsserver').get('tsconfigPath', 'tsconfig.json') let find = await workspace.findUp([tsconfigPath]) if (!find) { window.showMessage(`${tsconfigPath} not found!`, 'error') diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 58a431d..70d6230 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -19,17 +19,17 @@ import FormattingProvider from './features/formatting' import HoverProvider from './features/hover' import ImplementationsCodeLensProvider from './features/implementationsCodeLens' import ImportfixProvider from './features/importFix' +import TypeScriptInlayHintsProvider from './features/inlayHints' import InstallModuleProvider from './features/moduleInstall' import QuickfixProvider from './features/quickfix' import RefactorProvider from './features/refactor' import ReferenceProvider from './features/references' import ReferencesCodeLensProvider from './features/referencesCodeLens' import RenameProvider from './features/rename' -import SignatureHelpProvider from './features/signatureHelp' import SemanticTokensProvider from './features/semanticTokens' +import SignatureHelpProvider from './features/signatureHelp' import SmartSelection from './features/smartSelect' import TagClosing from './features/tagClosing' -import TypeScriptInlayHintsProvider from './features/inlayHints' import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' import { OrganizeImportsCodeActionProvider } from './organizeImports' import TypeScriptServiceClient from './typescriptServiceClient' diff --git a/src/server/utils/languageDescription.ts b/src/server/utils/languageDescription.ts index c3a09af..a796a9a 100644 --- a/src/server/utils/languageDescription.ts +++ b/src/server/utils/languageDescription.ts @@ -3,13 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as languageModeIds from './languageModeIds' +import path from 'path' +import { Uri } from 'coc.nvim' export interface LanguageDescription { readonly id: string readonly diagnosticSource: string readonly diagnosticLanguage: DiagnosticLanguage readonly modeIds: string[] - readonly configFile?: string readonly isExternal?: boolean readonly diagnosticOwner: string readonly configFilePattern?: RegExp @@ -26,10 +27,8 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ id: 'typescript', diagnosticSource: 'ts', diagnosticOwner: 'typescript', - modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact, - languageModeIds.typescripttsx, languageModeIds.typescriptjsx], diagnosticLanguage: DiagnosticLanguage.TypeScript, - configFile: 'tsconfig.json', + modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact, languageModeIds.typescripttsx, languageModeIds.typescriptjsx], configFilePattern: /^tsconfig(\..*)?\.json$/gi, standardFileExtensions: [ 'ts', @@ -42,9 +41,7 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ id: 'javascript', diagnosticSource: 'ts', diagnosticOwner: 'typescript', - modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact, languageModeIds.javascriptjsx], - diagnosticLanguage: DiagnosticLanguage.JavaScript, - configFile: 'jsconfig.json', + modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact, languageModeIds.javascriptjsx], diagnosticLanguage: DiagnosticLanguage.JavaScript, configFilePattern: /^jsconfig(\..*)?\.json$/gi, standardFileExtensions: [ 'js', @@ -56,3 +53,19 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ ] } ] + +export function isTsConfigFileName(fileName: string): boolean { + return /^tsconfig\.(.+\.)?json$/i.test(path.basename(fileName)) +} + +export function isJsConfigOrTsConfigFileName(fileName: string): boolean { + return /^[jt]sconfig\.(.+\.)?json$/i.test(path.basename(fileName)) +} + +export function doesResourceLookLikeATypeScriptFile(resource: Uri): boolean { + return /\.(tsx?|mts|cts)$/i.test(resource.fsPath) +} + +export function doesResourceLookLikeAJavaScriptFile(resource: Uri): boolean { + return /\.(jsx?|mjs|cjs)$/i.test(resource.fsPath) +} diff --git a/tsconfig.json b/tsconfig.json index 6464e65..40d3beb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ "allowUnreachableCode": true, "allowUnusedLabels": true, "forceConsistentCasingInFileNames": true, + "noEmit": true, "noImplicitAny": false, "noImplicitReturns": false, "noUnusedLocals": false, From 767b21b28e47a937ad01b29d0ad3f0a4dd3a89a3 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 25 Dec 2021 18:33:23 +0800 Subject: [PATCH 26/78] Release 1.9.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ee8dc8a..3cda392 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.3", + "version": "1.9.4", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From fc601dbd95a4481e143461680137e3eb50a3ca33 Mon Sep 17 00:00:00 2001 From: Heyward Fann Date: Thu, 30 Dec 2021 15:29:11 +0800 Subject: [PATCH 27/78] feat: allImportsAreUnused error can be converted to warning (#337) --- src/server/typescriptServiceClientHost.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index 9b575fe..9b04e72 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -22,6 +22,7 @@ import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus' const styleCheckDiagnostics = [ 6133, // variable is declared but never used 6138, // property is declared but its value is never read + 6192, // allImportsAreUnused 7027, // unreachable code detected 7028, // unused label 7029, // fall through case in switch From 3a992bc31625da5dc4020b42d1cbc30e241a0550 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 12 Jan 2022 04:12:31 +0800 Subject: [PATCH 28/78] upgrade dependencies Closes #342 --- package.json | 6 +-- yarn.lock | 147 ++++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 138 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 3cda392..e842dab 100644 --- a/package.json +++ b/package.json @@ -868,10 +868,10 @@ "author": "chemzqm@gmail.com", "license": "MIT", "devDependencies": { - "@types/node": "^10.12.0", + "@types/node": "^12.12.12", "coc.nvim": "^0.0.81-next.8", - "esbuild": "^0.8.29", - "semver": "^7.3.2", + "esbuild": "^0.14.11", + "semver": "^7.3.5", "vscode-languageserver-protocol": "^3.16.0", "which": "^2.0.2" }, diff --git a/yarn.lock b/yarn.lock index f1555d9..fced966 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,30 +2,148 @@ # yarn lockfile v1 -"@types/node@^10.12.0": - version "10.17.44" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.44.tgz#3945e6b702cb6403f22b779c8ea9e5c3f44ead40" - integrity sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw== +"@types/node@^12.12.12": + version "12.20.41" + resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.41.tgz#81d7734c5257da9f04354bd9084a6ebbdd5198a5" + integrity sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q== coc.nvim@^0.0.81-next.8: version "0.0.81-next.8" resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.8.tgz#7c0ec46cfab29d41ebdb9e3e9cabf1cb5cc2b920" integrity sha512-1dk2j571Gxk4lXw2GVdGfOJeZAhfZ5HgmtA/p4NW5gorNVnCcC6qe2sp0LiecgiWfah3o+BsOTahr+Vjwj6GYA== -esbuild@^0.8.29: - version "0.8.29" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.29.tgz#cc20fb752e0905a3546d68ae1be58f9b97044c39" - integrity sha512-UDsEoeXuctVgG2hEts1Hwq2jYDGqV7nksEHEZaiCy2v+lXF5ButX4ErPAJAFi5ZNKKW+6Pom93pArV7hki6HnQ== +esbuild-android-arm64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.11.tgz#b8b34e35a5b43880664ac7a3fbc70243d7ed894f" + integrity sha512-6iHjgvMnC/SzDH8TefL+/3lgCjYWwAd1LixYfmz/TBPbDQlxcuSkX0yiQgcJB9k+ibZ54yjVXziIwGdlc+6WNw== + +esbuild-darwin-64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.11.tgz#ba805de98c0412e50fcd0636451797da157b0625" + integrity sha512-olq84ikh6TiBcrs3FnM4eR5VPPlcJcdW8BnUz/lNoEWYifYQ+Po5DuYV1oz1CTFMw4k6bQIZl8T3yxL+ZT2uvQ== + +esbuild-darwin-arm64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.11.tgz#4d3573e448af76ce33e16231f3d9f878542d6fe8" + integrity sha512-Jj0ieWLREPBYr/TZJrb2GFH8PVzDqiQWavo1pOFFShrcmHWDBDrlDxPzEZ67NF/Un3t6sNNmeI1TUS/fe1xARg== + +esbuild-freebsd-64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.11.tgz#9294e6ab359ec93590ab097b0f2017de7c78ab4d" + integrity sha512-C5sT3/XIztxxz/zwDjPRHyzj/NJFOnakAanXuyfLDwhwupKPd76/PPHHyJx6Po6NI6PomgVp/zi6GRB8PfrOTA== + +esbuild-freebsd-arm64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.11.tgz#ae3e0b09173350b66cf8321583c9a1c1fcb8bb55" + integrity sha512-y3Llu4wbs0bk4cwjsdAtVOesXb6JkdfZDLKMt+v1U3tOEPBdSu6w8796VTksJgPfqvpX22JmPLClls0h5p+L9w== + +esbuild-linux-32@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.11.tgz#ddadbc7038aa5a6b1675bb1503cf79a0cbf1229a" + integrity sha512-Cg3nVsxArjyLke9EuwictFF3Sva+UlDTwHIuIyx8qpxRYAOUTmxr2LzYrhHyTcGOleLGXUXYsnUVwKqnKAgkcg== + +esbuild-linux-64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.11.tgz#d698e3ce3a231ddfeec6b5df8c546ae8883fcd88" + integrity sha512-oeR6dIrrojr8DKVrxtH3xl4eencmjsgI6kPkDCRIIFwv4p+K7ySviM85K66BN01oLjzthpUMvBVfWSJkBLeRbg== + +esbuild-linux-arm64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.11.tgz#85faea9fa99ad355b5e3b283197a4dfd0a110fe7" + integrity sha512-+e6ZCgTFQYZlmg2OqLkg1jHLYtkNDksxWDBWNtI4XG4WxuOCUErLqfEt9qWjvzK3XBcCzHImrajkUjO+rRkbMg== + +esbuild-linux-arm@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.11.tgz#74cbcf0b8a22c8401bcbcd6ebd4cbf2baca8b7b4" + integrity sha512-vcwskfD9g0tojux/ZaTJptJQU3a7YgTYsptK1y6LQ/rJmw7U5QJvboNawqM98Ca3ToYEucfCRGbl66OTNtp6KQ== + +esbuild-linux-mips64le@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.11.tgz#490429211a3233f5cbbd8575b7758b897e42979a" + integrity sha512-Rrs99L+p54vepmXIb87xTG6ukrQv+CzrM8eoeR+r/OFL2Rg8RlyEtCeshXJ2+Q66MXZOgPJaokXJZb9snq28bw== + +esbuild-linux-ppc64le@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.11.tgz#fc79d60710213b5b98345f5b138d48245616827a" + integrity sha512-JyzziGAI0D30Vyzt0HDihp4s1IUtJ3ssV2zx9O/c+U/dhUHVP2TmlYjzCfCr2Q6mwXTeloDcLS4qkyvJtYptdQ== + +esbuild-linux-s390x@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.11.tgz#ca4b93556bbba6cc95b0644f2ee93c982165ba07" + integrity sha512-DoThrkzunZ1nfRGoDN6REwmo8ZZWHd2ztniPVIR5RMw/Il9wiWEYBahb8jnMzQaSOxBsGp0PbyJeVLTUatnlcw== + +esbuild-netbsd-64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.11.tgz#edb340bc6653c88804cac2253e21b74258fce165" + integrity sha512-12luoRQz+6eihKYh1zjrw0CBa2aw3twIiHV/FAfjh2NEBDgJQOY4WCEUEN+Rgon7xmLh4XUxCQjnwrvf8zhACw== + +esbuild-openbsd-64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.11.tgz#caeff5f946f79a60ce7bcf88871ca4c71d3476e8" + integrity sha512-l18TZDjmvwW6cDeR4fmizNoxndyDHamGOOAenwI4SOJbzlJmwfr0jUgjbaXCUuYVOA964siw+Ix+A+bhALWg8Q== + +esbuild-sunos-64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.11.tgz#90ce7e1749c2958a53509b4bae7b8f7d98f276d6" + integrity sha512-bmYzDtwASBB8c+0/HVOAiE9diR7+8zLm/i3kEojUH2z0aIs6x/S4KiTuT5/0VKJ4zk69kXel1cNWlHBMkmavQg== + +esbuild-windows-32@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.11.tgz#d067f4ce15b29efba6336e6a23597120fafe49ec" + integrity sha512-J1Ys5hMid8QgdY00OBvIolXgCQn1ARhYtxPnG6ESWNTty3ashtc4+As5nTrsErnv8ZGUcWZe4WzTP/DmEVX1UQ== + +esbuild-windows-64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.11.tgz#13e86dd37a6cd61a5276fa2d271342d0f74da864" + integrity sha512-h9FmMskMuGeN/9G9+LlHPAoiQk9jlKDUn9yA0MpiGzwLa82E7r1b1u+h2a+InprbSnSLxDq/7p5YGtYVO85Mlg== + +esbuild-windows-arm64@0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.11.tgz#e8edfdf1d712085e6dc3fba18a0c225aaae32b75" + integrity sha512-dZp7Krv13KpwKklt9/1vBFBMqxEQIO6ri7Azf8C+ob4zOegpJmha2XY9VVWP/OyQ0OWk6cEeIzMJwInRZrzBUQ== + +esbuild@^0.14.11: + version "0.14.11" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.11.tgz#ac4acb78907874832afb704c3afe58ad37715c27" + integrity sha512-xZvPtVj6yecnDeFb3KjjCM6i7B5TCAQZT77kkW/CpXTMnd6VLnRPKrUB1XHI1pSq6a4Zcy3BGueQ8VljqjDGCg== + optionalDependencies: + esbuild-android-arm64 "0.14.11" + esbuild-darwin-64 "0.14.11" + esbuild-darwin-arm64 "0.14.11" + esbuild-freebsd-64 "0.14.11" + esbuild-freebsd-arm64 "0.14.11" + esbuild-linux-32 "0.14.11" + esbuild-linux-64 "0.14.11" + esbuild-linux-arm "0.14.11" + esbuild-linux-arm64 "0.14.11" + esbuild-linux-mips64le "0.14.11" + esbuild-linux-ppc64le "0.14.11" + esbuild-linux-s390x "0.14.11" + esbuild-netbsd-64 "0.14.11" + esbuild-openbsd-64 "0.14.11" + esbuild-sunos-64 "0.14.11" + esbuild-windows-32 "0.14.11" + esbuild-windows-64 "0.14.11" + esbuild-windows-arm64 "0.14.11" isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -semver@^7.3.2: - version "7.3.2" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" - integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +lru-cache@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" + integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + dependencies: + yallist "^4.0.0" + +semver@^7.3.5: + version "7.3.5" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" + integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== + dependencies: + lru-cache "^6.0.0" typescript@^4.5.4: version "4.5.4" @@ -56,3 +174,8 @@ which@^2.0.2: integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== dependencies: isexe "^2.0.0" + +yallist@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" + integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== From 4fa163e5548ea671fbbbb7570596d9fdaf86f193 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 12 Jan 2022 04:13:22 +0800 Subject: [PATCH 29/78] Release 1.9.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e842dab..add89ef 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.4", + "version": "1.9.5", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From d10bab9072a454816a57a78c7004a4db9f076277 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 13 Jan 2022 00:29:12 +0800 Subject: [PATCH 30/78] support referencesCodeLens.showOnAllFunctions Make codeLens same as VScode --- Readme.md | 2 + package.json | 12 ++ src/server/features/baseCodeLensProvider.ts | 54 ++++---- .../features/implementationsCodeLens.ts | 72 +++++----- src/server/features/referencesCodeLens.ts | 130 ++++++++++-------- src/server/languageProvider.ts | 4 +- src/server/typescriptService.ts | 4 +- 7 files changed, 151 insertions(+), 127 deletions(-) diff --git a/Readme.md b/Readme.md index ce776d5..6d2fc8a 100644 --- a/Readme.md +++ b/Readme.md @@ -145,6 +145,7 @@ for guide of coc.nvim's configuration. implementations, default: `true` - `typescript.referencesCodeLens.enable`:Enable codeLens for references, default: `true` +- `typescript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in typescript files. Default: `false` - `typescript.preferences.importModuleSpecifier` default: `"auto"` - `typescript.preferences.importModuleSpecifierEnding` default: `"auto"` - `typescript.preferences.quoteStyle` default: `"single"` @@ -209,6 +210,7 @@ for guide of coc.nvim's configuration. - `javascript.updateImportsOnFileMove.enable` default: `true` - `javascript.implementationsCodeLens.enable` default: `true` - `javascript.referencesCodeLens.enable` default: `true` +- `javascript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in JavaScript files default: `false` - `javascript.preferences.importModuleSpecifier` default: `"auto"` - `javascript.preferences.importModuleSpecifierEnding` default: `"auto"` - `javascript.preferences.quoteStyle` default: `"single"` diff --git a/package.json b/package.json index add89ef..eeaaad3 100644 --- a/package.json +++ b/package.json @@ -293,6 +293,12 @@ "default": true, "description": "Enable codeLens for references" }, + "typescript.referencesCodeLens.showOnAllFunctions": { + "type": "boolean", + "default": false, + "description": "Enable/disable references CodeLens on all functions in typescript files.", + "scope": "window" + }, "typescript.preferences.importModuleSpecifier": { "type": "string", "default": "shortest", @@ -547,6 +553,12 @@ "type": "boolean", "default": true }, + "javascript.referencesCodeLens.showOnAllFunctions": { + "type": "boolean", + "default": false, + "description": "Enable/disable references CodeLens on all functions in JavaScript files.", + "scope": "window" + }, "javascript.preferences.importModuleSpecifier": { "type": "string", "default": "shortest", diff --git a/src/server/features/baseCodeLensProvider.ts b/src/server/features/baseCodeLensProvider.ts index 74299d7..ff47b04 100644 --- a/src/server/features/baseCodeLensProvider.ts +++ b/src/server/features/baseCodeLensProvider.ts @@ -46,7 +46,8 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider public constructor( protected client: ITypeScriptServiceClient, - private cachedResponse: CachedNavTreeResponse + private cachedResponse: CachedNavTreeResponse, + protected modeId: string ) {} public get onDidChangeCodeLenses(): Event { @@ -116,38 +117,31 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider ) } } - protected getSymbolRange( - document: TextDocument, - item: Proto.NavigationTree - ): Range | null { - if (!item) { - return null - } +} - // TS 3.0+ provides a span for just the symbol - if ((item as any).nameSpan) { - return typeConverters.Range.fromTextSpan((item as any).nameSpan) - } +export function getSymbolRange( + document: TextDocument, + item: Proto.NavigationTree +): Range | null { + if (item.nameSpan) { + return typeConverters.Range.fromTextSpan(item.nameSpan) + } - // In older versions, we have to calculate this manually. See #23924 - const span = item.spans && item.spans[0] - if (!span) { - return null - } + // In older versions, we have to calculate this manually. See #23924 + const span = item.spans && item.spans[0] + if (!span) { + return null + } - const range = typeConverters.Range.fromTextSpan(span) - const text = document.getText(range) + const range = typeConverters.Range.fromTextSpan(span) + const text = document.getText(range) - const identifierMatch = new RegExp( - `^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, - 'gm' - ) - const match = identifierMatch.exec(text) - const prefixLength = match ? match.index + match[1].length : 0 - const startOffset = document.offsetAt(range.start) + prefixLength - return { - start: document.positionAt(startOffset), - end: document.positionAt(startOffset + item.text.length) - } + const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, 'gm') + const match = identifierMatch.exec(text) + const prefixLength = match ? match.index + match[1].length : 0 + const startOffset = document.offsetAt(range.start) + prefixLength + return { + start: document.positionAt(startOffset), + end: document.positionAt(startOffset + item.text.length) } } diff --git a/src/server/features/implementationsCodeLens.ts b/src/server/features/implementationsCodeLens.ts index 3e870c0..bb82ae9 100644 --- a/src/server/features/implementationsCodeLens.ts +++ b/src/server/features/implementationsCodeLens.ts @@ -7,7 +7,7 @@ import { TextDocument } from 'coc.nvim' import * as Proto from '../protocol' import * as PConst from '../protocol.const' import * as typeConverters from '../utils/typeConverters' -import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider' +import { TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider' export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider { public async resolveCodeLens( @@ -21,43 +21,39 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip filepath, codeLens.range.start ) - try { - const response = await this.client.execute('implementation', args, token, { lowPriority: true }) - if (response && response.type == 'response' && response.body) { - const locations = response.body - .map(reference => { - return { - uri: this.client.toResource(reference.file), - range: { - start: typeConverters.Position.fromLocation(reference.start), - end: { - line: reference.start.line, - character: 0 - } - } - } - }) - // Exclude original from implementations - .filter( - location => !( - location.uri.toString() === uri && - location.range.start.line === codeLens.range.start.line && - location.range.start.character === - codeLens.range.start.character - ) - ) - - codeLens.command = this.getCommand(locations, codeLens) - return codeLens + const response = await this.client.execute('implementation', args, token, { lowPriority: true }) + if (response.type !== 'response' || !response.body) { + codeLens.command = { + title: response.type === 'cancelled' + ? 'cancelled' + : 'could not determine implementation', + command: '' } - } catch { - // noop - } - - codeLens.command = { - title: '0 implementations', - command: '' + return codeLens } + const locations = response.body + .map(reference => { + return { + uri: this.client.toResource(reference.file), + range: { + start: typeConverters.Position.fromLocation(reference.start), + end: { + line: reference.start.line, + character: 0 + } + } + } + }) + // Exclude original from implementations + .filter( + location => !( + location.uri.toString() === uri && + location.range.start.line === codeLens.range.start.line && + location.range.start.character === + codeLens.range.start.character + ) + ) + codeLens.command = this.getCommand(locations, codeLens) return codeLens } @@ -84,7 +80,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip ): Range | null { switch (item.kind) { case PConst.Kind.interface: - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) case PConst.Kind.class: case PConst.Kind.method: @@ -92,7 +88,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip case PConst.Kind.memberGetAccessor: case PConst.Kind.memberSetAccessor: if (item.kindModifiers.match(/\babstract\b/g)) { - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) } break } diff --git a/src/server/features/referencesCodeLens.ts b/src/server/features/referencesCodeLens.ts index 5c31854..4d33193 100644 --- a/src/server/features/referencesCodeLens.ts +++ b/src/server/features/referencesCodeLens.ts @@ -2,15 +2,16 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CodeLens, Range } from 'vscode-languageserver-protocol' -import { TextDocument } from 'coc.nvim' +import { CancellationToken, Position, Range } from 'vscode-languageserver-protocol' +import { TextDocument, workspace, CodeLens } from 'coc.nvim' +import { ExecutionTarget } from '../typescriptService' import * as Proto from '../protocol' import * as PConst from '../protocol.const' import * as typeConverters from '../utils/typeConverters' -import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider' +import { TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider' export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider { - public resolveCodeLens( + public async resolveCodeLens( codeLens: CodeLens, token: CancellationToken ): Promise { @@ -20,47 +21,35 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase filepath, codeLens.range.start ) - return this.client - .execute('references', args, token, { - lowPriority: true - }) - .then(response => { - if (!response || response.type != 'response' || !response.body) { - throw codeLens - } + let response = await this.client.execute('references', args, token, { + lowPriority: true, + executionTarget: ExecutionTarget.Semantic + }) + if (!response || response.type != 'response' || !response.body) { + codeLens.command = { + title: response.type === 'cancelled' + ? 'cancelled' + : 'could not determine references', + command: '' + } + return codeLens + } - const locations = response.body.refs - .map(reference => - typeConverters.Location.fromTextSpan( - this.client.toResource(reference.file), - reference - ) - ) - .filter( - location => - // Exclude original definition from references - !( - location.uri.toString() === uri && - location.range.start.line === codeLens.range.start.line && - location.range.start.character === - codeLens.range.start.character - ) - ) + const locations = response.body.refs + .filter(reference => !reference.isDefinition) + .map(reference => + typeConverters.Location.fromTextSpan( + this.client.toResource(reference.file), + reference + ) + ) - codeLens.command = { - title: locations.length === 1 ? '1 reference' : `${locations.length} references`, - command: locations.length ? 'editor.action.showReferences' : '', - arguments: [uri, codeLens.range.start, locations] - } - return codeLens - }) - .catch(() => { - codeLens.command = { - title: '0 references', - command: '' - } - return codeLens - }) + codeLens.command = { + title: locations.length === 1 ? '1 reference' : `${locations.length} references`, + command: locations.length ? 'editor.action.showReferences' : '', + arguments: [uri, codeLens.range.start, locations] + } + return codeLens } protected extractSymbol( @@ -69,37 +58,68 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase parent: Proto.NavigationTree | null ): Range | null { if (parent && parent.kind === PConst.Kind.enum) { - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) } switch (item.kind) { + case PConst.Kind.function: { + const showOnAllFunctions = workspace.getConfiguration(this.modeId).get('referencesCodeLens.showOnAllFunctions') + if (showOnAllFunctions) { + return getSymbolRange(document, item) + } + } + // fallthrough + case PConst.Kind.const: case PConst.Kind.let: case PConst.Kind.variable: - case PConst.Kind.function: // Only show references for exported variables - if (!item.kindModifiers.match(/\bexport\b/)) { - break + if (/\bexport\b/.test(item.kindModifiers)) { + return getSymbolRange(document, item) } - // fallthrough + break case PConst.Kind.class: if (item.text === '') { break } - // fallthrough + return getSymbolRange(document, item) - case PConst.Kind.method: - case PConst.Kind.memberVariable: - case PConst.Kind.memberGetAccessor: - case PConst.Kind.memberSetAccessor: - case PConst.Kind.constructorImplementation: case PConst.Kind.interface: case PConst.Kind.type: case PConst.Kind.enum: - return super.getSymbolRange(document, item) + return getSymbolRange(document, item) + + case PConst.Kind.method: + case PConst.Kind.memberGetAccessor: + case PConst.Kind.memberSetAccessor: + case PConst.Kind.constructorImplementation: + case PConst.Kind.memberVariable: + // Don't show if child and parent have same start + // For https://github.com/microsoft/vscode/issues/90396 + if (parent && + comparePosition(typeConverters.Position.fromLocation(parent.spans[0].start), typeConverters.Position.fromLocation(item.spans[0].start)) == 0 + ) { + return null + } + + // Only show if parent is a class type object (not a literal) + switch (parent?.kind) { + case PConst.Kind.class: + case PConst.Kind.interface: + case PConst.Kind.type: + return getSymbolRange(document, item) + } + break } return null } } + +export function comparePosition(position: Position, other: Position): number { + if (position.line > other.line) return 1 + if (other.line == position.line && position.character > other.character) return 1 + if (other.line == position.line && position.character == other.character) return 0 + return -1 +} diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 70d6230..c171b57 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -149,10 +149,10 @@ export default class LanguageProvider { 'tsserver', [CodeActionKind.QuickFix])) let cachedResponse = new CachedNavTreeResponse() if (this.client.apiVersion.gte(API.v206) && conf.get('referencesCodeLens.enable')) { - this._register(languages.registerCodeLensProvider(languageIds, new ReferencesCodeLensProvider(client, cachedResponse))) + this._register(languages.registerCodeLensProvider(languageIds, new ReferencesCodeLensProvider(client, cachedResponse, this.description.id))) } if (this.client.apiVersion.gte(API.v220) && conf.get('implementationsCodeLens.enable')) { - this._register(languages.registerCodeLensProvider(languageIds, new ImplementationsCodeLensProvider(client, cachedResponse))) + this._register(languages.registerCodeLensProvider(languageIds, new ImplementationsCodeLensProvider(client, cachedResponse, this.description.id))) } if (this.client.apiVersion.gte(API.v350)) { this._register(languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client))) diff --git a/src/server/typescriptService.ts b/src/server/typescriptService.ts index 88a097b..f576567 100644 --- a/src/server/typescriptService.ts +++ b/src/server/typescriptService.ts @@ -34,7 +34,7 @@ export interface TypeScriptServerPlugin { readonly languages: string[] } -export enum ExectuionTarget { +export enum ExecutionTarget { Semantic, Syntax } @@ -43,7 +43,7 @@ export type ExecConfig = { readonly lowPriority?: boolean readonly nonRecoverable?: boolean readonly cancelOnResourceChange?: string - readonly executionTarget?: ExectuionTarget + readonly executionTarget?: ExecutionTarget } export interface TypeScriptRequestTypes { From b8430bd4bbade54a6f566c95e1253d7fdccc8773 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 13 Jan 2022 01:13:29 +0800 Subject: [PATCH 31/78] Check documentation as SymbolDisplayPart[] Closes #338 --- src/server/features/completionItemProvider.ts | 6 +++--- src/server/features/hover.ts | 14 ++++++++------ src/server/features/signatureHelp.ts | 8 ++++---- src/server/utils/previewer.ts | 15 ++++++++------- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/server/features/completionItemProvider.ts b/src/server/features/completionItemProvider.ts index 25733cf..8280ee2 100644 --- a/src/server/features/completionItemProvider.ts +++ b/src/server/features/completionItemProvider.ts @@ -251,7 +251,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP } const detail = details[0] if (!item.detail && detail.displayParts.length) { - item.detail = Previewer.plain(detail.displayParts) + item.detail = Previewer.plainWithLinks(detail.displayParts) } item.documentation = this.getDocumentation(detail) const { command, additionalTextEdits } = this.getCodeActions(detail, filepath) @@ -354,12 +354,12 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP private getDocumentation(detail: Proto.CompletionEntryDetails): MarkupContent | undefined { let documentation = '' if (detail.source) { - const importPath = `'${Previewer.plain(detail.source)}'` + const importPath = `'${Previewer.plainWithLinks(detail.source)}'` const autoImportLabel = `Auto import from ${importPath}` documentation += `${autoImportLabel}\n` } let parts = [ - Previewer.plain(detail.documentation), + Previewer.plainWithLinks(detail.documentation), Previewer.tagsMarkdownPreview(detail.tags) ] parts = parts.filter(s => s && s.trim() != '') diff --git a/src/server/features/hover.ts b/src/server/features/hover.ts index a9d3e26..c669f44 100644 --- a/src/server/features/hover.ts +++ b/src/server/features/hover.ts @@ -7,7 +7,7 @@ import { HoverProvider } from 'coc.nvim' import { CancellationToken, Hover, MarkedString, Position } from 'vscode-languageserver-protocol' import * as Proto from '../protocol' import { ITypeScriptServiceClient } from '../typescriptService' -import { tagsMarkdownPreview } from '../utils/previewer' +import { markdownDocumentation } from '../utils/previewer' import * as typeConverters from '../utils/typeConverters' export default class TypeScriptHoverProvider implements HoverProvider { @@ -42,14 +42,16 @@ export default class TypeScriptHoverProvider implements HoverProvider { } private static getContents(data: Proto.QuickInfoResponseBody): MarkedString[] { // tslint:disable-line - const parts = [] - + const parts: MarkedString[] = [] if (data.displayString) { + // const displayParts: string[] = [] parts.push({ language: 'typescript', value: data.displayString }) } - - const tags = tagsMarkdownPreview(data.tags) - parts.push(data.documentation + (tags ? '\n\n' + tags : '')) + const markup = markdownDocumentation(data.documentation, data.tags) + parts.push({ + language: 'markdown', + value: markup.value + }) return parts } } diff --git a/src/server/features/signatureHelp.ts b/src/server/features/signatureHelp.ts index c0a48d0..2a74a89 100644 --- a/src/server/features/signatureHelp.ts +++ b/src/server/features/signatureHelp.ts @@ -60,13 +60,13 @@ export default class TypeScriptSignatureHelpProvider implements SignatureHelpPro private convertSignature(item: Proto.SignatureHelpItem): SignatureInformation { let parameters = item.parameters.map(p => { return { - label: Previewer.plain(p.displayParts), + label: Previewer.plainWithLinks(p.displayParts), documentation: Previewer.markdownDocumentation(p.documentation, []) } }) - let label = Previewer.plain(item.prefixDisplayParts) - label += parameters.map(parameter => parameter.label).join(Previewer.plain(item.separatorDisplayParts)) - label += Previewer.plain(item.suffixDisplayParts) + let label = Previewer.plainWithLinks(item.prefixDisplayParts) + label += parameters.map(parameter => parameter.label).join(Previewer.plainWithLinks(item.separatorDisplayParts)) + label += Previewer.plainWithLinks(item.suffixDisplayParts) return { label, documentation: Previewer.markdownDocumentation( diff --git a/src/server/utils/previewer.ts b/src/server/utils/previewer.ts index ccae4f5..9ea0351 100644 --- a/src/server/utils/previewer.ts +++ b/src/server/utils/previewer.ts @@ -94,20 +94,15 @@ function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined { return label + (text.match(/\r\n|\n/g) ? ' \n' + text : ` — ${text}`) } -export function plain(parts: Proto.SymbolDisplayPart[]): string { - if (!parts || !parts.length) return '' - return parts.map(part => part.text).join('') -} - export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string { return (tags || []).map(getTagDocumentation).join(' \n\n') } export function markdownDocumentation( - documentation: Proto.SymbolDisplayPart[], + documentation: Proto.SymbolDisplayPart[] | string, tags: Proto.JSDocTagInfo[] ): MarkupContent { - let out = plain(documentation) + let out = plainWithLinks(documentation) const tagsPreview = tagsMarkdownPreview(tags) if (tagsPreview) { out = out + ('\n\n' + tagsPreview) @@ -118,6 +113,12 @@ export function markdownDocumentation( } } +export function plainWithLinks( + parts: readonly Proto.SymbolDisplayPart[] | string, +): string { + return processInlineTags(convertLinkTags(parts)) +} + /** * Convert `@link` inline tags to markdown links */ From 3248310ca4d7168d61f2754791e672594b70693b Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 14 Jan 2022 04:53:14 +0800 Subject: [PATCH 32/78] add history.md --- history.md | 212 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 history.md diff --git a/history.md b/history.md new file mode 100644 index 0000000..4ec8778 --- /dev/null +++ b/history.md @@ -0,0 +1,212 @@ +# 1.9.6 + +- Rework codeLens related. + +# 1.9.5 + +- Change 'allImportsAreUnused' diagnostic kind to warning. + +# 1.9.4 + +- Improve file pattern for config file. + +# 1.9.2 + +- Inlay hints support (#335) + +# 1.9.1 + +- use `TSS_DEBUG` & `TSS_DEBUG_BRK` for debug port + +# 1.9.0 + +- Add semanticTokens support #313 +- Add jsxAttributeCompletionStyle settings #319 +- Add command `tsserver.sortImports` #322 +- Add suggest.classMemberSnippets.enabled configuration cd16da8 +- Add suggest.jsdoc.generateReturns configuration 5a8c68f +- Add typescript.preferences.includePackageJsonAutoImports configuration 4d78b61 +- Add tsserver.enableTracing configuration 43e6f62 +- Add typescript.check.npmIsInstalled configuration 3bd84b1 + +# 1.8.3 + +- Support deprecated tag for document symbols, diagnostic, workspace symbols. + +# 1.8.2 + +- Support call hierarchy. +- Support `tags` and access modifier for document symbols. +- Support return `DefinitionLink[]` for definition provider. + +# 1.8.1 + +- Support `tsserver.tsconfigPath` configuration. + +# 1.8.0 + +- Support [Import Statement Completions](https://devblogs.microsoft.com/typescript/announcing-typescript-4-3/#import-statement-completions) + +# 1.7.0 + +- Support tag closing for JSX + +# 1.6.4 + +- Support `typescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces` and `ypescript.format.insertSpaceAfterOpeningAndBeforeClosingEmptyBraces` + +# 1.6.2 + +- Support languages from plugins. + +# 1.5.5 + +- Support `typescript.preferences.useAliasesForRenames` and `javascript.preferences.useAliasesForRenames` + +# 1.5.3 + +- Support the new path of Yarn v2 pnpify SDK. +- Us `tsserver.pluginPaths` replace `tsserver.pluginRoot`. + +# 1.5.0 + +- Support @ts-expect-error directive on tsserver v390. +- Support `tsserver.watchOptions` configuration. + +# 1.4.13 + +- Add `preferences.importModuleSpecifierEnding` configuration. +- Change `preferences.importModuleSpecifier` default to `auto`. + +# 1.4.12 + +- Support `tsserver.maxTsServerMemory` configuration. + +# 1.4.9 + +- Support semicolons format option. + +# 1.4.8 + +- support `format.enabled` configuration + +# 1.4.3 + +- Use global tsc when local tsc not foun + +# 1.4.0 + +- remove noSemicolons preferences + +# 1.3.15 + +- Add missing option "auto" to importModuleSpecifier + +# 1.3.11 + +- Add `tsserver.ignoreLocalTsserver` configuration. + +# 1.3.6 + +- Support `b:coc_tsserver_disable` + +# 1.3.2 + +- fix suggestionActions.enabled configuration not working + +# 1.3.1 + +- fix validate.enable not work sometimes + +# 1.3.0 + +- Loading status. +- Batched buffer synchronize. +- Configuration for showUnused variable. +- Smart selection support. +- Support 'auto' as quoteStyle. +- Support 'validateDefaultNpmLocation'. + +# 1.1.30 + +- rework of typescriptService, support interuptGetErr + +# 1.1.29 + +- Support plugin feature. + +# 1.1.28 + +- add codeAction provider for import missing node builtin modules. + +# 1.1.26 + +- Add install module codeAction for module not found diagnostic. +- Rework `tsserver.watchBuild`, use background process, support statusline. + +# 1.1.25 + +- Support autofix of node modules import + +# 1.1.23 + +- Add command `tsserver.executeAutofix` + +# 1.1.13 + +- Add triggerCharacters for SignatureHelp + +# 1.1.12 + +- Add typescript snippets from VSCode + +# 1.1.11 + +- Fix throw error of "No content available" on completion. + +# 1.1.10 + +- Support projectRootPath for document + +# 1.1.9 + +- Support commitCharacters of completion items + +# 1.1.8 + +- Add status bar support. + +# 1.1.7 + +- Add settings `javascript.validate.enable` and `typescript.validate.enable` + +# 1.1.6 + +- Fix suggestionActions.enabled not works + +# 1.1.5 + +- Use quickfix list for watchBuild errors + +# 1.1.4 + +- Fix organizeImports not working sometimes + +# 1.1.3 + +- Remove settings with `commaAfterImport`, use `typescript.preferences.noSemicolons` and `javasscript.preferences.noSemicolons` instead. + +# 1.1.2 + +- Support diagnostic of config file. + +# 1.1.1 + +- Remove unnecessary use of workspace terminal. + +# 1.1.0 + +- Support rename import path: https://code.visualstudio.com/updates/v1_28#_rename-import-path +- Use new `suggest` for completion configuration: https://code.visualstudio.com/updates/v1_28#_new-settings-for-jsts-suggestions +- Convert to async function: https://code.visualstudio.com/updates/v1_28#_convert-to-async-function +- Remove semicolons on format: set `typescript.preferences.noSemicolons` to true From e495357b5f8a820191db0d58cc30ec7f14a00e50 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 14 Jan 2022 04:55:35 +0800 Subject: [PATCH 33/78] Release 1.9.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eeaaad3..5ed2d6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.5", + "version": "1.9.6", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 0de25fe6f9e0c4fece7d884e75faa0b05844e906 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 19 Jan 2022 23:22:30 +0800 Subject: [PATCH 34/78] improve doc --- Readme.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Readme.md b/Readme.md index 6d2fc8a..5174b27 100644 --- a/Readme.md +++ b/Readme.md @@ -88,7 +88,9 @@ Almost the same as VSCode. - Code refactor using code actions. - Find references. - Signature help. +- Semantic tokens. - Rename symbols support. +- Automatic tag closing. - Rename imports on file rename, require [watchman](https://facebook.github.io/watchman/) installed in your \$PATH. - Search for workspace symbols. @@ -177,6 +179,7 @@ for guide of coc.nvim's configuration. - `typescript.suggest.jsdoc.generateReturns`: Enable/disable generating `@return` annotations for JSDoc templates. Requires using TypeScript 4.2+ in the workspace. default: `true` +- `typescript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` - `typescript.format.enabled`:Enable/disable format of typescript files. - `typescript.format.insertSpaceAfterCommaDelimiter` default: `true` - `typescript.format.insertSpaceAfterConstructor` default: `false` @@ -202,7 +205,6 @@ for guide of coc.nvim's configuration. - `typescript.format.insertSpaceAfterTypeAssertion` default: `false` - `typescript.format.placeOpenBraceOnNewLineForFunctions` default: `false` - `typescript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false` -- `typescript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` - `typescript.inlayHints`: inlayHints related options. - `javascript.format.enabled`: Enable/disable format for javascript files, default: `true` - `javascript.showUnused`: show unused variable hint, default: `true` @@ -236,6 +238,7 @@ for guide of coc.nvim's configuration. - `javascript.suggest.classMemberSnippets.enabled`: Enable/disable snippet completions for class members. Requires using TypeScript 4.5+ in the workspace, default: `true` +- `javascript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` - `javascript.format.insertSpaceAfterCommaDelimiter` default: `true` - `javascript.format.insertSpaceAfterConstructor` default: `false` - `javascript.format.insertSpaceAfterSemicolonInForStatements` default: `true` @@ -260,11 +263,12 @@ for guide of coc.nvim's configuration. - `javascript.format.insertSpaceAfterTypeAssertion` default: `false` - `javascript.format.placeOpenBraceOnNewLineForFunctions` default: `false` - `javascript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false` -- `javascript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` - `javascript.inlayHints`: inlayHints related options. -Configurations are the same as with VSCode. Try completion with `tsserver`, -`typescript` or `javascript` in your `coc-settings.json`. +Configurations are the same as with VSCode. Install +[coc-json](https://github.com/neoclide/coc-json) and try completion with +`tsserver`, `typescript` or `javascript` in your +`coc-settings.json`. ## Related extensions From d04366cc3008fc32e7b02ac4d1fbb4f35fd2a2a0 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 20 Jan 2022 04:04:13 +0800 Subject: [PATCH 35/78] Release 1.9.7 --- Readme.md | 4 ++-- history.md | 4 ++++ package.json | 10 ++++++---- src/server/features/tagClosing.ts | 18 ++++-------------- yarn.lock | 8 ++++---- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/Readme.md b/Readme.md index 5174b27..e161d42 100644 --- a/Readme.md +++ b/Readme.md @@ -160,7 +160,7 @@ for guide of coc.nvim's configuration. - `typescript.validate.enable`:Enable/disable TypeScript validation., default: `true` - `typescript.showUnused`: show unused variable hint, default: `true`. -- `typescript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false` +- `typescript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `true` - `typescript.suggest.enabled` default: `true` - `typescript.suggest.paths`:Enable/disable suggest paths in import statement and require calls, default: `true` @@ -208,7 +208,7 @@ for guide of coc.nvim's configuration. - `typescript.inlayHints`: inlayHints related options. - `javascript.format.enabled`: Enable/disable format for javascript files, default: `true` - `javascript.showUnused`: show unused variable hint, default: `true` -- `javascript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false` +- `javascript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `true` - `javascript.updateImportsOnFileMove.enable` default: `true` - `javascript.implementationsCodeLens.enable` default: `true` - `javascript.referencesCodeLens.enable` default: `true` diff --git a/history.md b/history.md index 4ec8778..de52c96 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,7 @@ +# 1.9.7 + +- Change default of `javascript.autoClosingTags` and `typescript.autoClosingTags` to `true`. + # 1.9.6 - Rework codeLens related. diff --git a/package.json b/package.json index 5ed2d6f..e80ac19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.6", + "version": "1.9.7", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", @@ -529,7 +529,8 @@ }, "typescript.autoClosingTags": { "type": "boolean", - "default": false + "default": true, + "description": "Enable/disable automatic closing of JSX tags." }, "javascript.showUnused": { "type": "boolean", @@ -851,7 +852,8 @@ }, "javascript.autoClosingTags": { "type": "boolean", - "default": false + "default": true, + "description": "Enable/disable automatic closing of JSX tags." }, "javascript.format.semicolons": { "type": "string", @@ -881,7 +883,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^12.12.12", - "coc.nvim": "^0.0.81-next.8", + "coc.nvim": "^0.0.81-next.11", "esbuild": "^0.14.11", "semver": "^7.3.5", "vscode-languageserver-protocol": "^3.16.0", diff --git a/src/server/features/tagClosing.ts b/src/server/features/tagClosing.ts index bb53810..b11ffb4 100644 --- a/src/server/features/tagClosing.ts +++ b/src/server/features/tagClosing.ts @@ -5,13 +5,10 @@ import { CancellationTokenSource, Disposable, disposeAll, Position, Range, snippetManager, events, workspace, InsertChange } from 'coc.nvim' import * as Proto from '../protocol' import { ITypeScriptServiceClient } from '../typescriptService' -import API from '../utils/api' import SnippetString from '../utils/SnippetString' import * as typeConverters from '../utils/typeConverters' export default class TagClosing implements Disposable { - public static readonly minVersion = API.v300 - private static _configurationLanguages: Record = { 'javascriptreact': 'javascript', 'typescriptreact': 'typescript', @@ -21,27 +18,21 @@ export default class TagClosing implements Disposable { private _disposed = false private _timeout: NodeJS.Timer | undefined = undefined private _cancel: CancellationTokenSource | undefined = undefined - private lastInsert: string constructor( private readonly client: ITypeScriptServiceClient, private readonly descriptionLanguageId: string ) { - events.on('InsertCharPre', character => { - this.lastInsert = character - }, null, this._disposables) - events.on('TextChangedI', this.onChange, this, this._disposables) - events.on('TextChangedP', this.onChange, this, this._disposables) + events.on('TextInsert', this.onInsertChange, this, this._disposables) } - private async onChange(bufnr: number, change: InsertChange): Promise { + private async onInsertChange(bufnr: number, change: InsertChange, lastInsert: string): Promise { let doc = workspace.getDocument((bufnr)) if (!doc || !doc.attached) return let enabled = this.isEnabled(doc.filetype, doc.uri) if (!enabled) return let { pre, changedtick, lnum } = change - if (!pre.endsWith('/') && !pre.endsWith('>')) return - if (!pre.endsWith(this.lastInsert)) return + if (lastInsert !== '/' && lastInsert != '>') return if (pre.length > 1 && pre[pre.length - 2] == '>') return const filepath = this.client.toOpenedFilePath(doc.uri) if (!filepath) return @@ -73,7 +64,6 @@ export default class TagClosing implements Disposable { return } if (this._disposed) return - const insertion = response.body if (doc.changedtick === changedtick) { snippetManager.insertSnippet( @@ -82,7 +72,7 @@ export default class TagClosing implements Disposable { Range.create(position, position) ) } - }, 50) + }, 30) } private isEnabled(languageId: string, uri: string): boolean { diff --git a/yarn.lock b/yarn.lock index fced966..5687257 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.41.tgz#81d7734c5257da9f04354bd9084a6ebbdd5198a5" integrity sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q== -coc.nvim@^0.0.81-next.8: - version "0.0.81-next.8" - resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.8.tgz#7c0ec46cfab29d41ebdb9e3e9cabf1cb5cc2b920" - integrity sha512-1dk2j571Gxk4lXw2GVdGfOJeZAhfZ5HgmtA/p4NW5gorNVnCcC6qe2sp0LiecgiWfah3o+BsOTahr+Vjwj6GYA== +coc.nvim@^0.0.81-next.11: + version "0.0.81-next.11" + resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.11.tgz#2ac44f770da662adbaf30678edc837715016fe90" + integrity sha512-XxRRdpK67tVJH+zvwi4bHc9e/UJWBj2ZJdY8Q9/Twp0tgipDstoCqK3KeoszT79J7whPzS/1Y/8VG7dRNrMJ4w== esbuild-android-arm64@0.14.11: version "0.14.11" From 021073e4fc346c3ecc751d683e670f97054acef3 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 20 Jan 2022 04:08:19 +0800 Subject: [PATCH 36/78] improve Readme --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index e161d42..ab150d7 100644 --- a/Readme.md +++ b/Readme.md @@ -88,6 +88,8 @@ Almost the same as VSCode. - Code refactor using code actions. - Find references. - Signature help. +- Call hierarchy. +- Selection range. - Semantic tokens. - Rename symbols support. - Automatic tag closing. From 4e9d171a2328269da718b13ed39a20fdd150390c Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 20 Jan 2022 21:29:43 +0800 Subject: [PATCH 37/78] Log document that can't perform semanticTokens --- history.md | 4 ++++ src/server/features/semanticTokens.ts | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/history.md b/history.md index de52c96..9bd595d 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,7 @@ +# 1.9.8 + +- Log to output when document content exceed limit of semantic tokens. + # 1.9.7 - Change default of `javascript.autoClosingTags` and `typescript.autoClosingTags` to `true`. diff --git a/src/server/features/semanticTokens.ts b/src/server/features/semanticTokens.ts index 683f64f..2ad3e49 100644 --- a/src/server/features/semanticTokens.ts +++ b/src/server/features/semanticTokens.ts @@ -29,9 +29,14 @@ export default class TypeScriptDocumentSemanticTokensProvider implements Documen } } + private logIgnored(uri: string): void { + this.client.logger.warn(`${uri} content length exceed limit 100000`) + } + async provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): Promise { const file = this.client.toOpenedFilePath(document.uri) if (!file || document.getText().length > CONTENT_LENGTH_LIMIT) { + this.logIgnored(document.uri) return null } return this._provideSemanticTokens(document, { file, start: 0, length: document.getText().length }, token) @@ -40,6 +45,7 @@ export default class TypeScriptDocumentSemanticTokensProvider implements Documen async provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): Promise { const file = this.client.toOpenedFilePath(document.uri) if (!file || (document.offsetAt(range.end) - document.offsetAt(range.start) > CONTENT_LENGTH_LIMIT)) { + this.logIgnored(document.uri) return null } From c6d7d127b63c08349e6854c11287b6f9f46a2934 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 20 Jan 2022 21:30:22 +0800 Subject: [PATCH 38/78] Release 1.9.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e80ac19..a7c9b95 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.7", + "version": "1.9.8", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 8a5e14f7667b1d5e3b37102736ae245d4e3b8c65 Mon Sep 17 00:00:00 2001 From: Ishan <95639928+ishanraychaudhuri@users.noreply.github.com> Date: Tue, 25 Jan 2022 14:24:02 -0500 Subject: [PATCH 39/78] Typo in filename (#349) --- src/server/typescriptServiceClient.ts | 2 +- src/utils/{fileSchemess.ts => fileSchemes.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/utils/{fileSchemess.ts => fileSchemes.ts} (100%) diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index acaa9ad..46cd5cf 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -7,7 +7,7 @@ import fs from 'fs' import os from 'os' import path from 'path' import { CancellationToken, CancellationTokenSource, Disposable, Emitter, Event } from 'vscode-languageserver-protocol' -import * as fileSchemes from '../utils/fileSchemess' +import * as fileSchemes from '../utils/fileSchemes' import { PluginManager } from '../utils/plugins' import { CallbackMap } from './callbackMap' import BufferSyncSupport from './features/bufferSyncSupport' diff --git a/src/utils/fileSchemess.ts b/src/utils/fileSchemes.ts similarity index 100% rename from src/utils/fileSchemess.ts rename to src/utils/fileSchemes.ts From ddcab27979058b7c60ac6a643a406c7999e8665c Mon Sep 17 00:00:00 2001 From: Tim Tyrrell Date: Thu, 27 Jan 2022 14:59:53 -0700 Subject: [PATCH 40/78] correct README importModuleSpecifier configuration examples (#350) The defaults of these two are actually [shortest](https://github.com/neoclide/coc-tsserver/blob/master/package.json#L304) --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index ab150d7..248509e 100644 --- a/Readme.md +++ b/Readme.md @@ -150,7 +150,7 @@ for guide of coc.nvim's configuration. - `typescript.referencesCodeLens.enable`:Enable codeLens for references, default: `true` - `typescript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in typescript files. Default: `false` -- `typescript.preferences.importModuleSpecifier` default: `"auto"` +- `typescript.preferences.importModuleSpecifier` default: `"shortest"` - `typescript.preferences.importModuleSpecifierEnding` default: `"auto"` - `typescript.preferences.quoteStyle` default: `"single"` - `typescript.preferences.includePackageJsonAutoImports`: Enable/disable @@ -215,7 +215,7 @@ for guide of coc.nvim's configuration. - `javascript.implementationsCodeLens.enable` default: `true` - `javascript.referencesCodeLens.enable` default: `true` - `javascript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in JavaScript files default: `false` -- `javascript.preferences.importModuleSpecifier` default: `"auto"` +- `javascript.preferences.importModuleSpecifier` default: `"shortest"` - `javascript.preferences.importModuleSpecifierEnding` default: `"auto"` - `javascript.preferences.quoteStyle` default: `"single"` - `javascript.validate.enable`: Enable/disable JavaScript validation., default: From e5f19b47b449ed48ba9d17bf8eca43a571f03aa2 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sun, 30 Jan 2022 18:00:29 +0800 Subject: [PATCH 41/78] use documentChanges for workspaceEdit --- src/server/features/refactor.ts | 17 +++++++++++++---- src/server/features/rename.ts | 6 +++--- src/server/organizeImports.ts | 2 +- src/server/utils/typeConverters.ts | 15 +++++++++++---- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/server/features/refactor.ts b/src/server/features/refactor.ts index 9a912f1..6c9fc4f 100644 --- a/src/server/features/refactor.ts +++ b/src/server/features/refactor.ts @@ -1,8 +1,8 @@ -import { CodeActionProvider, CodeActionProviderMetadata, commands, TextDocument, window, workspace } from 'coc.nvim' /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CodeActionProvider, Uri, CodeActionProviderMetadata, commands, TextDocument, window, workspace } from 'coc.nvim' import { CancellationToken, CodeAction, CodeActionContext, CodeActionKind, Range, WorkspaceEdit } from 'vscode-languageserver-protocol' import { Command, registCommand } from '../commands' import Proto from '../protocol' @@ -55,13 +55,22 @@ class ApplyRefactoringCommand implements Command { } private async toWorkspaceEdit(body: Proto.RefactorEditInfo): Promise { - for (const edit of body.edits) { - await workspace.createFile(edit.fileName, { ignoreIfExists: true }) - } let workspaceEdit = typeConverters.WorkspaceEdit.fromFileCodeEdits( this.client, body.edits ) + let documentChanges = workspaceEdit.documentChanges = workspaceEdit.documentChanges || [] + for (const edit of body.edits) { + let resource = this.client.toResource(edit.fileName) + if (Uri.parse(resource).scheme === 'file') { + // should create file first. + documentChanges.unshift({ + kind: 'create', + uri: resource, + options: { ignoreIfExists: true } + }) + } + } return workspaceEdit } } diff --git a/src/server/features/rename.ts b/src/server/features/rename.ts index 8df0966..cdb04dd 100644 --- a/src/server/features/rename.ts +++ b/src/server/features/rename.ts @@ -16,7 +16,7 @@ export default class TypeScriptRenameProvider implements RenameProvider { public constructor( private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager - ) { } + ) {} public async prepareRename( document: TextDocument, @@ -60,8 +60,8 @@ export default class TypeScriptRenameProvider implements RenameProvider { } if (this.client.apiVersion.gte(API.v310)) { - if ((renameInfo as any).fileToRename) { - const edits = await this.renameFile((renameInfo as any).fileToRename, newName, token) + if (renameInfo.fileToRename) { + const edits = await this.renameFile(renameInfo.fileToRename, newName, token) if (edits) { return edits } else { diff --git a/src/server/organizeImports.ts b/src/server/organizeImports.ts index 8f249af..c5d4ff7 100644 --- a/src/server/organizeImports.ts +++ b/src/server/organizeImports.ts @@ -39,7 +39,7 @@ export class OrganizeImportsCommand implements Command { client, response.body ) - let keys = Object.keys(edit.changes) + let keys = Object.keys(edit.changes || {}) if (keys.length == 1) { let doc = workspace.getDocument(keys[0]) if (doc) { diff --git a/src/server/utils/typeConverters.ts b/src/server/utils/typeConverters.ts index ace1aa1..6a07813 100644 --- a/src/server/utils/typeConverters.ts +++ b/src/server/utils/typeConverters.ts @@ -6,6 +6,7 @@ * Helpers for converting FROM LanguageServer types language-server ts types */ import * as language from 'vscode-languageserver-protocol' +import { TextDocumentEdit } from 'vscode-languageserver-protocol' import Proto from '../protocol' import * as PConst from '../protocol.const' import { ITypeScriptServiceClient } from '../typescriptService' @@ -98,14 +99,20 @@ export namespace WorkspaceEdit { client: ITypeScriptServiceClient, edits: Iterable ): language.WorkspaceEdit { - let changes = {} + let documentChanges: TextDocumentEdit[] = [] for (const edit of edits) { let uri = client.toResource(edit.fileName) - changes[uri] = edit.textChanges.map(change => { - return TextEdit.fromCodeEdit(change) + documentChanges.push({ + textDocument: { + uri, + version: null + }, + edits: edit.textChanges.map(change => { + return TextEdit.fromCodeEdit(change) + }) }) } - return { changes } + return { documentChanges } } } From c1a3cf47fed34f8ea6a5011463c0e2fc1b6d60e4 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sun, 30 Jan 2022 18:00:47 +0800 Subject: [PATCH 42/78] Release 1.9.9 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a7c9b95..83896fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.8", + "version": "1.9.9", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 78da8a35145c77ac00ce32896bf461a63316845e Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 16 Feb 2022 18:48:32 +0800 Subject: [PATCH 43/78] update Readme.md --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index 248509e..b21e24d 100644 --- a/Readme.md +++ b/Readme.md @@ -114,6 +114,8 @@ for guide of coc.nvim's configuration. - `tsserver.enable`:Enable tsserver extension, default: `true` - `tsserver.locale`:Locale of tsserver, default: `""` +- `tsserver.ignoreLocalTsserver`:Always use tsserver module from tsserver.tsdk + or coc-tsserver extension. - `tsserver.typingsCacheLocation`:Folder path for cache typings, default: `""` - `tsserver.formatOnType`:Run format on type special characters., default: `true` From 6cdbf56da529604da3221bb6fa30de3550505d02 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 16 Feb 2022 20:38:19 +0800 Subject: [PATCH 44/78] fix api with IServiceProvider Support for watch tsserver.enable configuration --- history.md | 9 +++ src/index.ts | 35 +-------- src/server/index.ts | 95 ++++++++++++++++++----- src/server/typescriptServiceClient.ts | 6 ++ src/server/typescriptServiceClientHost.ts | 4 - 5 files changed, 94 insertions(+), 55 deletions(-) diff --git a/history.md b/history.md index 9bd595d..37bded5 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,12 @@ +# 1.9.10 + +- Watch for `tsserver.enable` configuration to change service state. +- Fix tsserver not work well with `:CocList services` + +# 1.9.9 + +- Use documentChanges for workspaceEdit. + # 1.9.8 - Log to output when document content exceed limit of semantic tokens. diff --git a/src/index.ts b/src/index.ts index 28b7c42..113d965 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,5 @@ -import { commands, ExtensionContext, services, workspace } from 'coc.nvim' +import { ExtensionContext, services } from 'coc.nvim' import TsserverService from './server' -import { AutoFixCommand, Command, ConfigurePluginCommand, FileReferencesCommand, OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './server/commands' -import { OrganizeImportsCommand, SourceImportsCommand } from './server/organizeImports' import { PluginManager } from './utils/plugins' interface API { @@ -9,35 +7,10 @@ interface API { } export async function activate(context: ExtensionContext): Promise { - let { subscriptions, logger } = context - const config = workspace.getConfiguration().get('tsserver', {}) - if (!config.enable) return + let { subscriptions } = context const pluginManager = new PluginManager() - const service = new TsserverService(pluginManager) - function registCommand(cmd: Command): void { - let { id, execute } = cmd - subscriptions.push(commands.registerCommand(id as string, execute, cmd)) - } - registCommand(new ConfigurePluginCommand(pluginManager)) - registCommand(new AutoFixCommand(service)) - registCommand(new ReloadProjectsCommand(service)) - registCommand(new FileReferencesCommand(service)) - registCommand(new OpenTsServerLogCommand(service)) - registCommand(new TypeScriptGoToProjectConfigCommand(service)) - registCommand(new OrganizeImportsCommand(service)) - registCommand(new SourceImportsCommand(service)) - registCommand({ - id: 'tsserver.restart', - execute: (): void => { - service.restart() - } - }) - - service.start().then(() => { - subscriptions.push(services.regist(service)) - }, e => { - logger.error(`Error on service start:`, e) - }) + const service = new TsserverService(pluginManager, context.subscriptions) + subscriptions.push(services.regist(service)) return { configurePlugin: (pluginId: string, configuration: {}): void => { diff --git a/src/server/index.ts b/src/server/index.ts index 3637093..ce7698e 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,6 +1,8 @@ -import { disposeAll, IServiceProvider, ServiceStat, workspace, WorkspaceConfiguration } from 'coc.nvim' +import { commands, disposeAll, IServiceProvider, ServiceStat, workspace, WorkspaceConfiguration } from 'coc.nvim' import { Disposable, DocumentSelector, Emitter, Event } from 'vscode-languageserver-protocol' import { PluginManager } from '../utils/plugins' +import { AutoFixCommand, Command, ConfigurePluginCommand, FileReferencesCommand, OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './commands' +import { OrganizeImportsCommand, SourceImportsCommand } from './organizeImports' import TypeScriptServiceClientHost from './typescriptServiceClientHost' import { LanguageDescription, standardLanguageDescriptions } from './utils/languageDescription' @@ -10,23 +12,69 @@ export default class TsserverService implements IServiceProvider { public enable: boolean // supported language types public selector: DocumentSelector - public state = ServiceStat.Initial + public _state = ServiceStat.Initial public clientHost: TypeScriptServiceClientHost private _onDidServiceReady = new Emitter() public readonly onServiceReady: Event = this._onDidServiceReady.event private readonly disposables: Disposable[] = [] private descriptions: LanguageDescription[] = [] - constructor(private pluginManager: PluginManager) { + constructor(private pluginManager: PluginManager, private readonly subscriptions: Disposable[]) { const config = workspace.getConfiguration('tsserver') - const enableJavascript = !!config.get('enableJavascript') + const enableJavascript = config.get('enableJavascript', true) this.enable = config.get('enable') this.descriptions = standardLanguageDescriptions.filter(o => { return enableJavascript ? true : o.id != 'javascript' }) + workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('tsserver')) { + const config = workspace.getConfiguration('tsserver') + let enable = this.enable + this.enable = config.get('enable', true) + if (enable !== this.enable) { + if (this.enable) { + void this.start() + } else { + void this.stop() + } + } + } + }) this.selector = this.descriptions.reduce((arr, c) => { return arr.concat(c.modeIds) }, []) + this.registCommands() + } + + // public state = ServiceStat.Initial + + public get state(): ServiceStat { + if (this.clientHost) { + return this.clientHost.serviceClient.state + } + return this._state + } + + private registCommands(): void { + let { subscriptions } = this + const registCommand = (cmd: Command): void => { + let { id, execute } = cmd + subscriptions.push(commands.registerCommand(id as string, execute, cmd)) + } + registCommand(new ConfigurePluginCommand(this.pluginManager)) + registCommand(new AutoFixCommand(this)) + registCommand(new ReloadProjectsCommand(this)) + registCommand(new FileReferencesCommand(this)) + registCommand(new OpenTsServerLogCommand(this)) + registCommand(new TypeScriptGoToProjectConfigCommand(this)) + registCommand(new OrganizeImportsCommand(this)) + registCommand(new SourceImportsCommand(this)) + registCommand({ + id: 'tsserver.restart', + execute: (): void => { + this.restart() + } + }) } public get config(): WorkspaceConfiguration { @@ -52,38 +100,45 @@ export default class TsserverService implements IServiceProvider { } public start(): Promise { - if (this.clientHost) return - this.state = ServiceStat.Starting + if (!this.enable) return + if (this.clientHost) { + let client = this.clientHost.serviceClient + client.restartTsServer() + return + } + this._state = ServiceStat.Starting this.clientHost = new TypeScriptServiceClientHost(this.descriptions, this.pluginManager) - this.disposables.push(this.clientHost) let client = this.clientHost.serviceClient return new Promise(resolve => { client.onReady(() => { - Object.defineProperty(this, 'state', { - get: () => { - return this.clientHost.serviceClient.state - } - }) this._onDidServiceReady.fire(void 0) resolve() }) }) } - public dispose(): void { - disposeAll(this.disposables) - } - public restart(): void { - if (!this.clientHost) return - let client = this.clientHost.serviceClient - client.restartTsServer() + if (!this.enable) return + if (this.clientHost) { + let client = this.clientHost.serviceClient + client.restartTsServer() + } else { + void this.start() + } } public async stop(): Promise { if (!this.clientHost) return - this.clientHost.reset() let client = this.clientHost.serviceClient await client.stop() + this.clientHost.dispose() + this.clientHost = null + this._state = ServiceStat.Stopped + } + + public dispose(): void { + void this.stop() + this._onDidServiceReady.dispose() + disposeAll(this.disposables) } } diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index 46cd5cf..4e6dc7d 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -34,6 +34,7 @@ export interface TsDiagnostics { export default class TypeScriptServiceClient implements ITypeScriptServiceClient { private token: number = 0 + private noRestart = false public state = ServiceStat.Initial public readonly logger: Logger = new Logger() public readonly bufferSyncSupport: BufferSyncSupport @@ -169,6 +170,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient tsServerProcess.onExit(() => { resolve() }) + this.noRestart = true tsServerProcess.kill() } else { resolve() @@ -381,6 +383,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient this._callbacks = new CallbackMap() this._requestQueue = new RequestQueue() this._pendingResponses = new Set() + if (this.noRestart) { + this.noRestart = false + return + } if (restart) { const diff = Date.now() - this.lastStart this.numberRestarts++ diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index 9b04e72..277efb5 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -160,10 +160,6 @@ export default class TypeScriptServiceClientHost implements Disposable { this.ataProgressReporter.dispose() } - public reset(): void { - this.fileConfigurationManager.reset() - } - public get serviceClient(): TypeScriptServiceClient { return this.client } From 2d62d7da5fafe7e6a46d8833e6cd28d9c0a17671 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 16 Feb 2022 20:41:58 +0800 Subject: [PATCH 45/78] Release 1.9.10 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83896fb..c4b10f6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.9", + "version": "1.9.10", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 464adfcb0a3f5960151b09f1b8d2c2c35ae78e1b Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 18 Feb 2022 03:46:45 +0800 Subject: [PATCH 46/78] use Tsserver_path for resolved tsc path Closes #306 --- src/server/index.ts | 9 +++++---- src/server/typescriptServiceClient.ts | 9 +++++++-- src/server/typescriptServiceClientHost.ts | 4 ++-- src/server/utils/versionProvider.ts | 9 +++++++++ 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index ce7698e..160bce2 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -99,20 +99,21 @@ export default class TsserverService implements IServiceProvider { }) } - public start(): Promise { + public async start(): Promise { if (!this.enable) return if (this.clientHost) { let client = this.clientHost.serviceClient client.restartTsServer() return } + let tscPath = await workspace.nvim.getVar('Tsserver_path') as string this._state = ServiceStat.Starting - this.clientHost = new TypeScriptServiceClientHost(this.descriptions, this.pluginManager) + this.clientHost = new TypeScriptServiceClientHost(this.descriptions, this.pluginManager, tscPath) let client = this.clientHost.serviceClient - return new Promise(resolve => { + await new Promise(resolve => { client.onReady(() => { this._onDidServiceReady.fire(void 0) - resolve() + resolve(undefined) }) }) } diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index 4e6dc7d..cd29468 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -70,7 +70,8 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient constructor( public readonly pluginManager: PluginManager, - public readonly modeIds: string[] + public readonly modeIds: string[], + private readonly tscPathVim: string | undefined ) { this.pathSeparator = path.sep this.lastStart = Date.now() @@ -217,7 +218,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private startService(resendModels = false): ForkedTsServerProcess | undefined { const { ignoreLocalTsserver } = this.configuration let currentVersion: TypeScriptVersion - if (!ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion() + console.log('===========') + console.log(this.tscPathVim) + if (this.tscPathVim) currentVersion = this.versionProvider.getVersionFromTscPath(this.tscPathVim) + if (!currentVersion && !ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion() if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) { this.info('Local tsserver not found, using bundled tsserver with coc-tsserver.') currentVersion = this.versionProvider.getDefaultVersion() @@ -232,6 +236,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } this._apiVersion = currentVersion.version this._tscPath = currentVersion.tscPath + workspace.nvim.setVar('Tsserver_path', this._tscPath, true) this.versionStatus.onDidChangeTypeScriptVersion(currentVersion) const tsServerForkArgs = this.getTsServerArgs(currentVersion) const options = { execArgv: this.getExecArgv() } diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index 277efb5..4113e09 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -38,7 +38,7 @@ export default class TypeScriptServiceClientHost implements Disposable { private readonly fileConfigurationManager: FileConfigurationManager private reportStyleCheckAsWarnings = true - constructor(descriptions: LanguageDescription[], pluginManager: PluginManager) { + constructor(descriptions: LanguageDescription[], pluginManager: PluginManager, tscPath: string | undefined) { let timer: NodeJS.Timer const handleProjectChange = () => { if (timer) clearTimeout(timer) @@ -57,7 +57,7 @@ export default class TypeScriptServiceClientHost implements Disposable { packageFileWatcher.onDidChange(handleProjectChange, this, this.disposables) const allModeIds = this.getAllModeIds(descriptions, pluginManager) - this.client = new TypeScriptServiceClient(pluginManager, allModeIds) + this.client = new TypeScriptServiceClient(pluginManager, allModeIds, tscPath) this.disposables.push(this.client) this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => { this.diagnosticsReceived(kind, resource, diagnostics).catch(e => { diff --git a/src/server/utils/versionProvider.ts b/src/server/utils/versionProvider.ts index 02e4a21..b488bef 100644 --- a/src/server/utils/versionProvider.ts +++ b/src/server/utils/versionProvider.ts @@ -106,6 +106,15 @@ export class TypeScriptVersionProvider { return undefined } + public getVersionFromTscPath(tscPath: string): TypeScriptVersion | undefined { + if (!tscPath || !fs.existsSync(tscPath)) return undefined + let libFolder = path.resolve(tscPath, '../../lib') + if (fs.existsSync(libFolder)) { + let version = new TypeScriptVersion(libFolder) + if (version.isValid) return version + } + } + public getLocalVersion(): TypeScriptVersion | undefined { let folders = workspace.workspaceFolders.map(f => Uri.parse(f.uri).fsPath) for (let p of folders) { From 4f480eda46166a2d3d366ae55d43e9d77bf5a31f Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 18 Feb 2022 03:52:22 +0800 Subject: [PATCH 47/78] Release 1.9.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c4b10f6..a8aee00 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.10", + "version": "1.9.11", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 7633da18eb9840d9ac7cfb7d43e8047b8cecb37f Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 18 Feb 2022 21:37:00 +0800 Subject: [PATCH 48/78] clear highlight on client dispose --- src/server/typescriptServiceClient.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index cd29468..e2bb565 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -138,9 +138,14 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient public dispose(): void { this.tsServerProcess.kill() + this.diagnosticsManager.dispose() this.bufferSyncSupport.dispose() this.logger.dispose() this._onTsServerStarted.dispose() + this._onProjectLanguageServiceStateChanged.dispose() + this._onDidBeginInstallTypings.dispose() + this._onDidEndInstallTypings.dispose() + this._onTypesInstallerInitializationFailed.dispose() this._onResendModelsRequested.dispose() this.versionStatus.dispose() } From 5dcd715888efd5d8dc5cd8787ca9c3427c5badab Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 18 Feb 2022 21:37:25 +0800 Subject: [PATCH 49/78] Release 1.9.12 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8aee00..84527ec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.11", + "version": "1.9.12", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 67239f40b5a19e2a8ae143fdfd3108c926e672cc Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 19 Feb 2022 02:31:42 +0800 Subject: [PATCH 50/78] remove console.log --- src/server/typescriptServiceClient.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index e2bb565..dc3565c 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -223,8 +223,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private startService(resendModels = false): ForkedTsServerProcess | undefined { const { ignoreLocalTsserver } = this.configuration let currentVersion: TypeScriptVersion - console.log('===========') - console.log(this.tscPathVim) if (this.tscPathVim) currentVersion = this.versionProvider.getVersionFromTscPath(this.tscPathVim) if (!currentVersion && !ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion() if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) { From 2d4c8f0bb37b043aeddfae3b69cc99520f8ea53e Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 22 Feb 2022 18:11:13 +0800 Subject: [PATCH 51/78] fix possible start multiply tsserver Closes #355 --- src/server/index.ts | 8 ++++---- src/server/typescriptServiceClientHost.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index 160bce2..6744848 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -100,14 +100,14 @@ export default class TsserverService implements IServiceProvider { } public async start(): Promise { - if (!this.enable) return + if (!this.enable || this._state == ServiceStat.Starting) return + this._state = ServiceStat.Starting if (this.clientHost) { let client = this.clientHost.serviceClient client.restartTsServer() return } - let tscPath = await workspace.nvim.getVar('Tsserver_path') as string - this._state = ServiceStat.Starting + let tscPath = await workspace.nvim.getVar('Tsserver_path') as string | null this.clientHost = new TypeScriptServiceClientHost(this.descriptions, this.pluginManager, tscPath) let client = this.clientHost.serviceClient await new Promise(resolve => { @@ -132,7 +132,7 @@ export default class TsserverService implements IServiceProvider { if (!this.clientHost) return let client = this.clientHost.serviceClient await client.stop() - this.clientHost.dispose() + this.clientHost?.dispose() this.clientHost = null this._state = ServiceStat.Stopped } diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index 4113e09..dd44d49 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -38,7 +38,7 @@ export default class TypeScriptServiceClientHost implements Disposable { private readonly fileConfigurationManager: FileConfigurationManager private reportStyleCheckAsWarnings = true - constructor(descriptions: LanguageDescription[], pluginManager: PluginManager, tscPath: string | undefined) { + constructor(descriptions: LanguageDescription[], pluginManager: PluginManager, tscPath: string | null) { let timer: NodeJS.Timer const handleProjectChange = () => { if (timer) clearTimeout(timer) From 9097ecf7479fa2fb67e10967a5627f812aa69543 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Tue, 22 Feb 2022 18:11:43 +0800 Subject: [PATCH 52/78] Release 1.9.13 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 84527ec..7020c0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.12", + "version": "1.9.13", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 8958a6d83c951a158d0090f0a6080b005bf0aecf Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Mon, 28 Feb 2022 00:40:14 +0800 Subject: [PATCH 53/78] add javascript snippets --- package.json | 8 ++ snippets/javascript.json | 194 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) create mode 100644 snippets/javascript.json diff --git a/package.json b/package.json index 7020c0b..9b0d0b0 100644 --- a/package.json +++ b/package.json @@ -876,6 +876,14 @@ { "language": "typescriptreact", "path": "./snippets/typescript.json" + }, + { + "language": "javascript", + "path": "./snippets/javascript.json" + }, + { + "language": "javascriptreact", + "path": "./snippets/javascript.json" } ] }, diff --git a/snippets/javascript.json b/snippets/javascript.json new file mode 100644 index 0000000..ac5cd55 --- /dev/null +++ b/snippets/javascript.json @@ -0,0 +1,194 @@ +{ + "define module": { + "prefix": "define", + "body": [ + "define([", + "\t'require',", + "\t'${1:dependency}'", + "], function(require, ${2:factory}) {", + "\t'use strict';", + "\t$0", + "});" + ], + "description": "define module" + }, + "For Loop": { + "prefix": "for", + "body": [ + "for (let ${1:index} = 0; ${1:index} < ${2:array}.length; ${1:index}++) {", + "\tconst ${3:element} = ${2:array}[${1:index}];", + "\t$TM_SELECTED_TEXT$0", + "}" + ], + "description": "For Loop" + }, + "For-Each Loop": { + "prefix": "foreach", + "body": [ + "${1:array}.forEach(${2:element} => {", + "\t$TM_SELECTED_TEXT$0", + "});" + ], + "description": "For-Each Loop" + }, + "For-In Loop": { + "prefix": "forin", + "body": [ + "for (const ${1:key} in ${2:object}) {", + "\tif (Object.hasOwnProperty.call(${2:object}, ${1:key})) {", + "\t\tconst ${3:element} = ${2:object}[${1:key}];", + "\t\t$TM_SELECTED_TEXT$0", + "\t}", + "}" + ], + "description": "For-In Loop" + }, + "For-Of Loop": { + "prefix": "forof", + "body": [ + "for (const ${1:iterator} of ${2:object}) {", + "\t$TM_SELECTED_TEXT$0", + "}" + ], + "description": "For-Of Loop" + }, + "Function Statement": { + "prefix": "function", + "body": [ + "function ${1:name}(${2:params}) {", + "\t$TM_SELECTED_TEXT$0", + "}" + ], + "description": "Function Statement" + }, + "If Statement": { + "prefix": "if", + "body": [ + "if (${1:condition}) {", + "\t$TM_SELECTED_TEXT$0", + "}" + ], + "description": "If Statement" + }, + "If-Else Statement": { + "prefix": "ifelse", + "body": [ + "if (${1:condition}) {", + "\t$TM_SELECTED_TEXT$0", + "} else {", + "\t", + "}" + ], + "description": "If-Else Statement" + }, + "New Statement": { + "prefix": "new", + "body": [ + "const ${1:name} = new ${2:type}(${3:arguments});$0" + ], + "description": "New Statement" + }, + "Switch Statement": { + "prefix": "switch", + "body": [ + "switch (${1:key}) {", + "\tcase ${2:value}:", + "\t\t$0", + "\t\tbreak;", + "", + "\tdefault:", + "\t\tbreak;", + "}" + ], + "description": "Switch Statement" + }, + "While Statement": { + "prefix": "while", + "body": [ + "while (${1:condition}) {", + "\t$TM_SELECTED_TEXT$0", + "}" + ], + "description": "While Statement" + }, + "Do-While Statement": { + "prefix": "dowhile", + "body": [ + "do {", + "\t$TM_SELECTED_TEXT$0", + "} while (${1:condition});" + ], + "description": "Do-While Statement" + }, + "Try-Catch Statement": { + "prefix": "trycatch", + "body": [ + "try {", + "\t$TM_SELECTED_TEXT$0", + "} catch (${1:error}) {", + "\t", + "}" + ], + "description": "Try-Catch Statement" + }, + "Set Timeout Function": { + "prefix": "settimeout", + "body": [ + "setTimeout(() => {", + "\t$TM_SELECTED_TEXT$0", + "}, ${1:timeout});" + ], + "description": "Set Timeout Function" + }, + "Set Interval Function": { + "prefix": "setinterval", + "body": [ + "setInterval(() => {", + "\t$TM_SELECTED_TEXT$0", + "}, ${1:interval});" + ], + "description": "Set Interval Function" + }, + "Import external module.": { + "prefix": "import statement", + "body": [ + "import { $0 } from \"${1:module}\";" + ], + "description": "Import external module." + }, + "Region Start": { + "prefix": "#region", + "body": [ + "//#region $0" + ], + "description": "Folding Region Start" + }, + "Region End": { + "prefix": "#endregion", + "body": [ + "//#endregion" + ], + "description": "Folding Region End" + }, + "Log to the console": { + "prefix": "log", + "body": [ + "console.log($1);" + ], + "description": "Log to the console" + }, + "Log warning to console": { + "prefix": "warn", + "body": [ + "console.warn($1);" + ], + "description": "Log warning to the console" + }, + "Log error to console": { + "prefix": "error", + "body": [ + "console.error($1);" + ], + "description": "Log error to the console" + } +} From c4be180558108e501b1214d548922e8736144d09 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 10 Mar 2022 15:43:20 +0800 Subject: [PATCH 54/78] fix tsserver.restart not work --- src/server/index.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/server/index.ts b/src/server/index.ts index 6744848..d1866f6 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -118,14 +118,10 @@ export default class TsserverService implements IServiceProvider { }) } - public restart(): void { + public async restart(): Promise { if (!this.enable) return - if (this.clientHost) { - let client = this.clientHost.serviceClient - client.restartTsServer() - } else { - void this.start() - } + await this.stop() + await this.start() } public async stop(): Promise { From 6d598560c63c86652ceb9616104c12cb8625a649 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 10 Mar 2022 15:43:43 +0800 Subject: [PATCH 55/78] Release 1.9.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9b0d0b0..17e2831 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.13", + "version": "1.9.14", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From c5d9e07d3ca6afcb86c7e6ffb84e5042c832f5f2 Mon Sep 17 00:00:00 2001 From: Calvin Huang Date: Sat, 26 Mar 2022 16:42:53 -0700 Subject: [PATCH 56/78] Update typescriptServiceClient.ts (#360) --- src/server/typescriptServiceClient.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index dc3565c..5c6c58c 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -431,6 +431,9 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } public toResource(filepath: string): string { + if (filepath.includes('zipfile:')) { + return filepath.replace(/.*zipfile:/, 'zipfile://'); + } if (this._apiVersion.gte(API.v213)) { if (filepath.startsWith(this.inMemoryResourcePrefix + 'untitled:')) { let resource = Uri.parse(filepath) From 9d7ee9da1f5d5ad82128c7d93a85bfbedee43a99 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sun, 27 Mar 2022 07:45:50 +0800 Subject: [PATCH 57/78] Release 1.9.15 --- history.md | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/history.md b/history.md index 37bded5..4372440 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,16 @@ +# 1.9.15 + +- Fix uri for `zipfile`. + +# 1.9.14 + +- Add javascript snippets +- Fix command `tsserver.restart` not work + +# 1.9.11 + +- Resued resolved tsserver path after `:CocRestart` + # 1.9.10 - Watch for `tsserver.enable` configuration to change service state. diff --git a/package.json b/package.json index 17e2831..9325281 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.14", + "version": "1.9.15", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From b22c59c072884ea87fc8ff1aba5a93ab80d26f41 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 2 Apr 2022 05:45:18 +0800 Subject: [PATCH 58/78] better log --- src/server/utils/logger.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/server/utils/logger.ts b/src/server/utils/logger.ts index 4169cec..81b0314 100644 --- a/src/server/utils/logger.ts +++ b/src/server/utils/logger.ts @@ -51,12 +51,24 @@ export default class Logger { this.logLevel('Error', message, data) } + private now(): string { + const now = new Date() + return padLeft(now.getUTCHours() + '', 2, '0') + + ':' + padLeft(now.getMinutes() + '', 2, '0') + + ':' + padLeft(now.getUTCSeconds() + '', 2, '0') + '.' + now.getMilliseconds() + } + public logLevel(level: string, message: string, data?: any): void { this.output.appendLine( - `[${level} - ${new Date().toLocaleTimeString()}] ${message}` + `[${level} - ${this.now()}] ${message}` ) if (data) { this.output.appendLine(this.data2String(data)) } } } + + +function padLeft(s: string, n: number, pad = ' ') { + return pad.repeat(Math.max(0, n - s.length)) + s +} From 3448f5237a4c197c568249f0bd8c3cf012db02d7 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 6 Apr 2022 15:53:46 +0800 Subject: [PATCH 59/78] upgrade typescript --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9325281..3c23390 100644 --- a/package.json +++ b/package.json @@ -898,6 +898,6 @@ "which": "^2.0.2" }, "dependencies": { - "typescript": "^4.5.4" + "typescript": "^4.6.3" } } diff --git a/yarn.lock b/yarn.lock index 5687257..d538c56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -145,10 +145,10 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" -typescript@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== +typescript@^4.6.3: + version "4.6.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" + integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== vscode-jsonrpc@6.0.0: version "6.0.0" From 9dc9f5bd6af79793b616109a67689277265489d2 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 6 Apr 2022 15:57:51 +0800 Subject: [PATCH 60/78] repsect isSnippet from CompletionEntry Closes #362 --- src/server/utils/completionItem.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/server/utils/completionItem.ts b/src/server/utils/completionItem.ts index 1ccfec2..499df4a 100644 --- a/src/server/utils/completionItem.ts +++ b/src/server/utils/completionItem.ts @@ -59,6 +59,9 @@ export function convertCompletionEntry( insertText = label insertTextFormat = InsertTextFormat.Snippet } + if (tsEntry.isSnippet) { + insertTextFormat = InsertTextFormat.Snippet + } let textEdit: TextEdit | null = null if (tsEntry.replacementSpan) { From 07ee36caf6e184817b9fdc0907e691e1dcc635a3 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 6 Apr 2022 15:58:14 +0800 Subject: [PATCH 61/78] Release 1.9.16 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c23390..236ca18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.15", + "version": "1.9.16", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From fb9bde0c08ac88b056a37f03ab52b530f3ff97e2 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 8 Apr 2022 22:40:10 +0800 Subject: [PATCH 62/78] support SymbolKind.Constructor and localFunction as snippet --- src/server/features/documentSymbol.ts | 1 + src/server/utils/completionItem.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/src/server/features/documentSymbol.ts b/src/server/features/documentSymbol.ts index 98a4586..3a78abc 100644 --- a/src/server/features/documentSymbol.ts +++ b/src/server/features/documentSymbol.ts @@ -38,6 +38,7 @@ const getSymbolKind = (kind: string): SymbolKind => { return SymbolKind.Variable case PConst.Kind.constructSignature: case PConst.Kind.constructorImplementation: + return SymbolKind.Constructor case PConst.Kind.function: case PConst.Kind.localFunction: return SymbolKind.Function diff --git a/src/server/utils/completionItem.ts b/src/server/utils/completionItem.ts index 499df4a..0bd8ab4 100644 --- a/src/server/utils/completionItem.ts +++ b/src/server/utils/completionItem.ts @@ -139,6 +139,7 @@ function convertKind(kind: string): CompletionItemKind { case PConst.Kind.memberSetAccessor: return CompletionItemKind.Field case PConst.Kind.function: + case PConst.Kind.localFunction: return CompletionItemKind.Function case PConst.Kind.method: case PConst.Kind.constructSignature: From 2fc8fb014a591a74ee56b80e0019d072c47c87ee Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 8 Apr 2022 22:40:45 +0800 Subject: [PATCH 63/78] Release 1.9.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 236ca18..b428726 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.16", + "version": "1.9.17", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 148a55d63c6e8ec5509ca7fdd3e8ca3e9fd3bf08 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sun, 10 Apr 2022 15:07:17 +0800 Subject: [PATCH 64/78] jsDocCompletion feature --- Readme.md | 5 + history.md | 5 + package.json | 10 ++ src/server/features/jsDocCompletion.ts | 121 +++++++++++++++++++++++++ src/server/languageProvider.ts | 9 +- 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 src/server/features/jsDocCompletion.ts diff --git a/Readme.md b/Readme.md index b21e24d..c368060 100644 --- a/Readme.md +++ b/Readme.md @@ -269,6 +269,11 @@ for guide of coc.nvim's configuration. - `javascript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false` - `javascript.inlayHints`: inlayHints related options. +### Added on 1.10.0 + +- `javascript.suggest.completeJSDocs` `typescript.suggest.completeJSDocs`: + Enable/disable suggestion to complete JSDoc comments. default: `true` + Configurations are the same as with VSCode. Install [coc-json](https://github.com/neoclide/coc-json) and try completion with `tsserver`, `typescript` or `javascript` in your diff --git a/history.md b/history.md index 4372440..9dee661 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,8 @@ +# 1.10.0 + +- Support jsdoc completion. +- Add configurations `javascript.suggest.completeJSDocs` and `typescript.suggest.completeJSDocs`. + # 1.9.15 - Fix uri for `zipfile`. diff --git a/package.json b/package.json index b428726..703aea1 100644 --- a/package.json +++ b/package.json @@ -865,6 +865,16 @@ "insert", "remove" ] + }, + "javascript.suggest.completeJSDocs": { + "type": "boolean", + "default": true, + "description": "Enable/disable suggestion to complete JSDoc comments." + }, + "typescript.suggest.completeJSDocs": { + "type": "boolean", + "default": true, + "description": "Enable/disable suggestion to complete JSDoc comments." } } }, diff --git a/src/server/features/jsDocCompletion.ts b/src/server/features/jsDocCompletion.ts new file mode 100644 index 0000000..22872aa --- /dev/null +++ b/src/server/features/jsDocCompletion.ts @@ -0,0 +1,121 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken, CompletionItem, CompletionItemKind, CompletionItemProvider, InsertTextFormat, Position, Range, SnippetString, TextDocument, workspace } from 'coc.nvim' +import { ITypeScriptServiceClient } from '../typescriptService' +import { LanguageDescription } from '../utils/languageDescription' +import * as typeConverters from '../utils/typeConverters' +import FileConfigurationManager from './fileConfigurationManager' + +const defaultJsDoc = new SnippetString(`/**\n * $0\n */`) + +function createCompleteItem(document: TextDocument, position: Position): CompletionItem { + const line = document.lineAt(position.line).text + const prefix = line.slice(0, position.character).match(/\/\**\s*$/) + const suffix = line.slice(position.character).match(/^\s*\**\//) + const start = Position.create(position.line, prefix ? position.character - prefix[0].length : position.character) + const range = Range.create(start, Position.create(start.line, start.character + (suffix ? suffix[0].length : 0))) + let insert = `/** */` + return { + label: insert, + kind: CompletionItemKind.Text, + insertTextFormat: InsertTextFormat.Snippet, + detail: 'JSDoc comment', + sortText: `\0`, + textEdit: { + newText: insert, + range + } + } +} + +export class JsDocCompletionProvider implements CompletionItemProvider { + constructor( + private readonly client: ITypeScriptServiceClient, + private readonly language: LanguageDescription, + private readonly fileConfigurationManager: FileConfigurationManager, + ) {} + + public async provideCompletionItems( + document: TextDocument, + position: Position, + token: CancellationToken + ): Promise { + if (!workspace.getConfiguration(this.language.id, document.uri).get('suggest.completeJSDocs')) { + return undefined + } + + const file = this.client.toOpenedFilePath(document.uri) + if (!file) { + return undefined + } + + if (!this.isPotentiallyValidDocCompletionPosition(document, position)) { + return undefined + } + + const response = await this.client.interruptGetErr(async () => { + await this.fileConfigurationManager.ensureConfigurationForDocument(document, token) + const args = typeConverters.Position.toFileLocationRequestArgs(file, position) + return this.client.execute('docCommentTemplate', args, token) + }) + if (response.type !== 'response' || !response.body) { + return undefined + } + + const item = createCompleteItem(document, position) + + // Workaround for #43619 + // docCommentTemplate previously returned undefined for empty jsdoc templates. + // TS 2.7 now returns a single line doc comment, which breaks indentation. + if (response.body.newText === '/** */') { + item.textEdit.newText = defaultJsDoc.value + } else { + item.textEdit.newText = templateToSnippet(response.body.newText).value + } + + return [item] + } + + private isPotentiallyValidDocCompletionPosition( + document: TextDocument, + position: Position + ): boolean { + // Only show the JSdoc completion when the everything before the cursor is whitespace + // or could be the opening of a comment + const line = document.lineAt(position.line).text + const prefix = line.slice(0, position.character) + if (!/^\s*$|\/\*\s*$|^\s*\/\*+\s*$/.test(prefix)) { + return false + } + + // And everything after is possibly a closing comment or more whitespace + const suffix = line.slice(position.character) + return /^\s*(\*+\/)?\s*$/.test(suffix) + } +} + +export function templateToSnippet(template: string): SnippetString { + // TODO: use append placeholder + let snippetIndex = 1 + template = template.replace(/\*\s$/gm, '*') + template = template.replace(/\$/g, '\\$') + template = template.replace(/^[ \t]*(?=(\/|[ ]\*))/gm, '') + template = template.replace(/^(\/\*\*\s*\*[ ]*)$/m, (x) => x + `\$0`) + template = template.replace(/\* @param([ ]\{\S+\})?\s+(\S+)[ \t]*$/gm, (_param, type, post) => { + let out = '* @param ' + if (type === ' {any}' || type === ' {*}') { + out += `{\$\{${snippetIndex++}:*\}} ` + } else if (type) { + out += type + ' ' + } + out += post + ` \${${snippetIndex++}}` + return out + }) + + template = template.replace(/\* @returns[ \t]*$/gm, `* @returns \${${snippetIndex++}}`) + + return new SnippetString(template) +} diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index c171b57..4a58133 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -31,6 +31,7 @@ import SignatureHelpProvider from './features/signatureHelp' import SmartSelection from './features/smartSelect' import TagClosing from './features/tagClosing' import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' +import { JsDocCompletionProvider } from './features/jsDocCompletion' import { OrganizeImportsCodeActionProvider } from './organizeImports' import TypeScriptServiceClient from './typescriptServiceClient' import API from './utils/api' @@ -73,13 +74,19 @@ export default class LanguageProvider { typingsStatus: TypingsStatus ): void { let languageIds = this.description.modeIds - let clientId = `tsserver-${this.description.id}` + let clientId = `tsc-${this.description.id}` this._register( languages.registerCompletionItemProvider(clientId, 'TSC', languageIds, new CompletionItemProvider(client, typingsStatus, this.fileConfigurationManager, this.description.id), CompletionItemProvider.triggerCharacters ) ) + this._register( + languages.registerCompletionItemProvider(`tsc-${this.description.id}-jsdoc`, 'TSC', languageIds, + new JsDocCompletionProvider(client, this.description, this.fileConfigurationManager), + ['*', ' '] + ) + ) if (this.client.apiVersion.gte(API.v230)) { this._register(languages.registerCompletionItemProvider( `${this.description.id}-directive`, From 0cd99b05cc4c243c7b9b0edfc154a2ebd1036cca Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sun, 10 Apr 2022 15:07:41 +0800 Subject: [PATCH 65/78] Release 1.10.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 703aea1..296ad6f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.9.17", + "version": "1.10.0", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 2fd4948bd5ca6adef67bd99563db3662c43d5cef Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sun, 10 Apr 2022 15:15:32 +0800 Subject: [PATCH 66/78] add typescript.suggest.objectLiteralMethodSnippets.enabled --- Readme.md | 5 +++++ package.json | 12 ++++++++++++ src/server/features/fileConfigurationManager.ts | 2 ++ 3 files changed, 19 insertions(+) diff --git a/Readme.md b/Readme.md index c368060..4cea2e2 100644 --- a/Readme.md +++ b/Readme.md @@ -274,6 +274,11 @@ for guide of coc.nvim's configuration. - `javascript.suggest.completeJSDocs` `typescript.suggest.completeJSDocs`: Enable/disable suggestion to complete JSDoc comments. default: `true` +### Added on 1.10.1 +- `typescript.suggest.objectLiteralMethodSnippets.enabled` + `javascript.suggest.objectLiteralMethodSnippets.enabled`: + Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace + Configurations are the same as with VSCode. Install [coc-json](https://github.com/neoclide/coc-json) and try completion with `tsserver`, `typescript` or `javascript` in your diff --git a/package.json b/package.json index 296ad6f..c8bd588 100644 --- a/package.json +++ b/package.json @@ -875,6 +875,18 @@ "type": "boolean", "default": true, "description": "Enable/disable suggestion to complete JSDoc comments." + }, + "javascript.suggest.objectLiteralMethodSnippets.enabled": { + "type": "boolean", + "default": true, + "description": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace", + "scope": "resource" + }, + "typescript.suggest.objectLiteralMethodSnippets.enabled": { + "type": "boolean", + "default": true, + "description": "Enable/disable snippet completions for methods in object literals. Requires using TypeScript 4.7+ in the workspace", + "scope": "resource" } } }, diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 1c5b94d..42d9acc 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -161,6 +161,8 @@ export default class FileConfigurationManager { paths: config.get('paths', true), completeFunctionCalls: config.get('completeFunctionCalls', true), autoImports: config.get('autoImports', true), + // @ts-expect-error until 4.7 + includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), generateReturnInDocTemplate: config.get('jsdoc.generateReturns', true), importStatementSuggestions: config.get('importStatements', true), includeCompletionsForImportStatements: config.get('includeCompletionsForImportStatements', true), From dfd41e13689c27ba1db2128339ef05d075965a39 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 20 Apr 2022 21:12:53 +0800 Subject: [PATCH 67/78] use cached format options --- package.json | 2 +- src/server/features/fileConfigurationManager.ts | 8 +++++++- yarn.lock | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index c8bd588..4a97f46 100644 --- a/package.json +++ b/package.json @@ -913,7 +913,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^12.12.12", - "coc.nvim": "^0.0.81-next.11", + "coc.nvim": "^0.0.81-next.23", "esbuild": "^0.14.11", "semver": "^7.3.5", "vscode-languageserver-protocol": "^3.16.0", diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 42d9acc..49184c0 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -87,7 +87,13 @@ export default class FileConfigurationManager { } public async ensureConfigurationForDocument(document: TextDocument, token: CancellationToken): Promise { - let opts = await workspace.getFormatOptions(document.uri) + let opts: { insertSpaces: boolean, tabSize: number } + let cached = this.cachedMap.get(document.uri) + if (cached) { + opts = { insertSpaces: cached.formatOptions.convertTabsToSpaces, tabSize: cached.formatOptions.tabSize } + } else { + opts = await workspace.getFormatOptions(document.uri) + } return this.ensureConfigurationOptions(document, opts.insertSpaces, opts.tabSize, token) } diff --git a/yarn.lock b/yarn.lock index d538c56..a37f5dc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.41.tgz#81d7734c5257da9f04354bd9084a6ebbdd5198a5" integrity sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q== -coc.nvim@^0.0.81-next.11: - version "0.0.81-next.11" - resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.11.tgz#2ac44f770da662adbaf30678edc837715016fe90" - integrity sha512-XxRRdpK67tVJH+zvwi4bHc9e/UJWBj2ZJdY8Q9/Twp0tgipDstoCqK3KeoszT79J7whPzS/1Y/8VG7dRNrMJ4w== +coc.nvim@^0.0.81-next.23: + version "0.0.81-next.23" + resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.23.tgz#48d8238afaaa0738c6237d8c077ba1791e2f90c6" + integrity sha512-RxNx6iRz7UvdgDeMVLyNXP6xe8GU8aY0Qxl/sBI+m2RzAJDRKCPuyFU1uOSxZntLglWtzKd87k3Byymdm19uBQ== esbuild-android-arm64@0.14.11: version "0.14.11" From c8373ddaf435952c969bea1ea82731cb599abc32 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Wed, 20 Apr 2022 21:13:13 +0800 Subject: [PATCH 68/78] Release 1.10.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a97f46..2439c82 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.10.0", + "version": "1.10.1", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From f27b21cb52fa3924f8f71d790df757e68bda956d Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Thu, 21 Apr 2022 23:39:43 +0800 Subject: [PATCH 69/78] fix log format --- src/server/utils/logger.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/utils/logger.ts b/src/server/utils/logger.ts index 81b0314..0d30ca5 100644 --- a/src/server/utils/logger.ts +++ b/src/server/utils/logger.ts @@ -60,7 +60,7 @@ export default class Logger { public logLevel(level: string, message: string, data?: any): void { this.output.appendLine( - `[${level} - ${this.now()}] ${message}` + `[${level} - ${this.now()}] ${message}` ) if (data) { this.output.appendLine(this.data2String(data)) From 4dbde8211c2ff7344660b9e7ad2fd9ec76da7619 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Mon, 2 May 2022 14:44:37 +0800 Subject: [PATCH 70/78] fix bad insertText Closes #363 --- src/server/utils/completionItem.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/utils/completionItem.ts b/src/server/utils/completionItem.ts index 0bd8ab4..4c2f920 100644 --- a/src/server/utils/completionItem.ts +++ b/src/server/utils/completionItem.ts @@ -76,7 +76,7 @@ export function convertCompletionEntry( if (tsEntry.kindModifiers) { const kindModifiers = new Set(tsEntry.kindModifiers.split(/,|\s+/g)) if (kindModifiers.has(PConst.KindModifiers.optional)) { - insertText = label + insertText = insertText ?? label label += '?' } From d27643a27ce6ff5a4728dd35bcae0ad405e66223 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Mon, 2 May 2022 14:52:49 +0800 Subject: [PATCH 71/78] Release 1.10.2 --- history.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/history.md b/history.md index 9dee661..0f9243d 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,12 @@ +# 1.10.2 + +- Fix snippet completion not work for optional complete item. + +# 1.10.1 + +- Avoid unnecessary fetch of format option. +- Add `typescript.suggest.objectLiteralMethodSnippets.enabled` + # 1.10.0 - Support jsdoc completion. diff --git a/package.json b/package.json index 2439c82..484ecc5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.10.1", + "version": "1.10.2", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 3a41bbe0450fcad769e75d3988c13b7dc56c2032 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 6 May 2022 15:05:06 +0800 Subject: [PATCH 72/78] rework inlayHints features Use languages.registerInlayHintsProvider API --- package.json | 2 +- .../features/fileConfigurationManager.ts | 24 ++- src/server/features/inlayHints.ts | 171 +++++------------- src/server/index.ts | 2 +- src/server/languageProvider.ts | 14 +- src/server/typescriptServiceClientHost.ts | 6 +- src/server/utils/languageDescription.ts | 6 +- yarn.lock | 8 +- 8 files changed, 81 insertions(+), 152 deletions(-) diff --git a/package.json b/package.json index 484ecc5..2ca9468 100644 --- a/package.json +++ b/package.json @@ -913,7 +913,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^12.12.12", - "coc.nvim": "^0.0.81-next.23", + "coc.nvim": "^0.0.81-next.25", "esbuild": "^0.14.11", "semver": "^7.3.5", "vscode-languageserver-protocol": "^3.16.0", diff --git a/src/server/features/fileConfigurationManager.ts b/src/server/features/fileConfigurationManager.ts index 49184c0..f6674a6 100644 --- a/src/server/features/fileConfigurationManager.ts +++ b/src/server/features/fileConfigurationManager.ts @@ -42,6 +42,7 @@ export interface SuggestOptions { readonly includeCompletionsWithSnippetText: boolean readonly includeCompletionsWithClassMemberSnippets: boolean readonly generateReturnInDocTemplate: boolean + readonly includeCompletionsWithObjectLiteralMethodSnippets: boolean } export default class FileConfigurationManager { @@ -167,7 +168,6 @@ export default class FileConfigurationManager { paths: config.get('paths', true), completeFunctionCalls: config.get('completeFunctionCalls', true), autoImports: config.get('autoImports', true), - // @ts-expect-error until 4.7 includeCompletionsWithObjectLiteralMethodSnippets: config.get('suggest.objectLiteralMethodSnippets.enabled', true), generateReturnInDocTemplate: config.get('jsdoc.generateReturns', true), importStatementSuggestions: config.get('importStatements', true), @@ -182,26 +182,31 @@ export default class FileConfigurationManager { if (this.client.apiVersion.lt(API.v290)) { return {} } - const config = workspace.getConfiguration(`${language}.preferences`, uri) + const config = workspace.getConfiguration(language, uri) + const preferencesConfig = workspace.getConfiguration(`${language}.preferences`, uri) const suggestConfig = this.getCompleteOptions(language) // getImportModuleSpecifierEndingPreference available on ts 2.9.0 const preferences: Proto.UserPreferences = { - quotePreference: this.getQuoteStyle(config), - importModuleSpecifierPreference: getImportModuleSpecifier(config) as any, - importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(config), - jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(config), + quotePreference: this.getQuoteStyle(preferencesConfig), + importModuleSpecifierPreference: getImportModuleSpecifier(preferencesConfig) as any, + importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig), + jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(preferencesConfig), allowTextChangesInNewFiles: uri.startsWith('file:'), allowRenameOfImportPath: true, // can't support it with coc.nvim by now. provideRefactorNotApplicableReason: false, - providePrefixAndSuffixTextForRename: config.get('renameShorthandProperties', true) === false ? false : config.get('useAliasesForRenames', true), + providePrefixAndSuffixTextForRename: preferencesConfig.get('renameShorthandProperties', true) === false ? false : preferencesConfig.get('useAliasesForRenames', true), generateReturnInDocTemplate: suggestConfig.generateReturnInDocTemplate, includeCompletionsForImportStatements: suggestConfig.includeCompletionsForImportStatements, includeCompletionsWithClassMemberSnippets: suggestConfig.includeCompletionsWithClassMemberSnippets, includeCompletionsWithSnippetText: suggestConfig.includeCompletionsWithSnippetText, + // @ts-expect-error until 4.7 + includeCompletionsWithObjectLiteralMethodSnippets: suggestConfig.includeCompletionsWithObjectLiteralMethodSnippets, + includeAutomaticOptionalChainCompletions: suggestConfig.includeAutomaticOptionalChainCompletions, + useLabelDetailsInCompletionEntries: true, allowIncompleteCompletions: true, displayPartsForJSDoc: true, - ...getInlayHintsPreferences(language), + ...getInlayHintsPreferences(config), } return preferences } @@ -259,8 +264,7 @@ export class InlayHintSettingNames { static readonly enumMemberValuesEnabled = 'inlayHints.enumMemberValues.enabled' } -export function getInlayHintsPreferences(language: string) { - const config = workspace.getConfiguration(language) +export function getInlayHintsPreferences(config: WorkspaceConfiguration) { return { includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config), includeInlayParameterNameHintsWhenArgumentMatchesName: !config.get(InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName, true), diff --git a/src/server/features/inlayHints.ts b/src/server/features/inlayHints.ts index 30ad69a..4ab268e 100644 --- a/src/server/features/inlayHints.ts +++ b/src/server/features/inlayHints.ts @@ -3,149 +3,56 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CancellationToken, CancellationTokenSource, Disposable, disposeAll, Document, Position, Range, TextDocument, workspace } from 'coc.nvim' +import { CancellationToken, Disposable, disposeAll, Emitter, Event, InlayHint, InlayHintKind, InlayHintsProvider, Range, TextDocument, workspace } from 'coc.nvim' import type * as Proto from '../protocol' import { ITypeScriptServiceClient } from '../typescriptService' import API from '../utils/api' +import { LanguageDescription } from '../utils/languageDescription' +import * as typeConverters from '../utils/typeConverters' import FileConfigurationManager, { getInlayHintsPreferences } from './fileConfigurationManager' -export enum InlayHintKind { - Other = 0, - Type = 1, - Parameter = 2 -} - -export interface InlayHint { - text: string - position: Position - kind: InlayHintKind - whitespaceBefore?: boolean - whitespaceAfter?: boolean -} - -export default class TypeScriptInlayHintsProvider implements Disposable { +export default class TypeScriptInlayHintsProvider implements InlayHintsProvider { public static readonly minVersion = API.v440 - private readonly inlayHintsNS = workspace.createNameSpace('tsserver-inlay-hint') - - private _disposables: Disposable[] = [] - private _tokenSource: CancellationTokenSource | undefined = undefined - private _inlayHints: Map = new Map() - - public dispose() { - if (this._tokenSource) { - this._tokenSource.cancel() - this._tokenSource.dispose() - this._tokenSource = undefined - } - - disposeAll(this._disposables) - this._disposables = [] - this._inlayHints.clear() - } + private disposables: Disposable[] = [] + private readonly _onDidChangeInlayHints = new Emitter() + public readonly onDidChangeInlayHints: Event = this._onDidChangeInlayHints.event constructor( + private readonly language: LanguageDescription, private readonly client: ITypeScriptServiceClient, private readonly fileConfigurationManager: FileConfigurationManager, - private readonly languageIds: string[] ) { - let languageId = this.languageIds[0] - let section = `${languageId}.inlayHints` + let section = `${language.id}.inlayHints` workspace.onDidChangeConfiguration(async e => { if (e.affectsConfiguration(section)) { - for (let doc of workspace.documents) { - if (!this.inlayHintsEnabled(languageId)) { - doc.buffer.clearNamespace(this.inlayHintsNS) - } else { - await this.syncAndRenderHints(doc) - } - } + this._onDidChangeInlayHints.fire() } - }, null, this._disposables) - workspace.onDidOpenTextDocument(async e => { - const doc = workspace.getDocument(e.bufnr) - await this.syncAndRenderHints(doc) - }, null, this._disposables) - - workspace.onDidChangeTextDocument(async e => { - const doc = workspace.getDocument(e.bufnr) - if (this.languageIds.includes(doc.textDocument.languageId)) { - this.renderHintsForAllDocuments() + }, null, this.disposables) + // When a JS/TS file changes, change inlay hints for all visible editors + // since changes in one file can effect the hints the others. + workspace.onDidChangeTextDocument(e => { + let doc = workspace.getDocument(e.textDocument.uri) + if (language.languageIds.includes(doc.languageId)) { + this._onDidChangeInlayHints.fire() } - }, null, this._disposables) - - this.renderHintsForAllDocuments() + }, null, this.disposables) } - private async renderHintsForAllDocuments(): Promise { - for (let doc of workspace.documents) { - await this.syncAndRenderHints(doc) - } - } - - private async syncAndRenderHints(doc: Document) { - if (!this.languageIds.includes(doc.textDocument.languageId)) return - if (!this.inlayHintsEnabled(this.languageIds[0])) return - - if (this._tokenSource) { - this._tokenSource.cancel() - this._tokenSource.dispose() - } - - try { - this._tokenSource = new CancellationTokenSource() - const { token } = this._tokenSource - const range = Range.create(0, 0, doc.lineCount, doc.getline(doc.lineCount).length) - const hints = await this.provideInlayHints(doc.textDocument, range, token) - if (token.isCancellationRequested) return - - await this.renderHints(doc, hints) - } catch (e) { - console.error(e) - this._tokenSource.cancel() - this._tokenSource.dispose() - } - } - - private async renderHints(doc: Document, hints: InlayHint[]) { - this._inlayHints.set(doc.uri, hints) - - const chaining_hints = {} - for (const item of hints) { - const chunks: [[string, string]] = [[item.text, 'CocHintSign']] - if (chaining_hints[item.position.line] === undefined) { - chaining_hints[item.position.line] = chunks - } else { - chaining_hints[item.position.line].push([' ', 'Normal']) - chaining_hints[item.position.line].push(chunks[0]) - } - } - - doc.buffer.clearNamespace(this.inlayHintsNS) - Object.keys(chaining_hints).forEach(async (line) => { - await doc.buffer.setVirtualText(this.inlayHintsNS, Number(line), chaining_hints[line], {}) - }) - } - - private inlayHintsEnabled(language: string) { - const preferences = getInlayHintsPreferences(language) - return preferences.includeInlayParameterNameHints === 'literals' - || preferences.includeInlayParameterNameHints === 'all' - || preferences.includeInlayEnumMemberValueHints - || preferences.includeInlayFunctionLikeReturnTypeHints - || preferences.includeInlayFunctionParameterTypeHints - || preferences.includeInlayPropertyDeclarationTypeHints - || preferences.includeInlayVariableTypeHints + public dispose(): void { + this._onDidChangeInlayHints.dispose() + disposeAll(this.disposables) } async provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise { const filepath = this.client.toOpenedFilePath(document.uri) if (!filepath) return [] + if (!areInlayHintsEnabledForFile(this.language, document)) { + return [] + } const start = document.offsetAt(range.start) const length = document.offsetAt(range.end) - start - await this.fileConfigurationManager.ensureConfigurationForDocument(document, token) - const response = await this.client.execute('provideInlayHints', { file: filepath, start, length }, token) if (response.type !== 'response' || !response.success || !response.body) { return [] @@ -153,11 +60,11 @@ export default class TypeScriptInlayHintsProvider implements Disposable { return response.body.map(hint => { return { - text: hint.text, - position: Position.create(hint.position.line - 1, hint.position.offset - 1), - kind: hint.kind && fromProtocolInlayHintKind(hint.kind), - whitespaceAfter: hint.whitespaceAfter, - whitespaceBefore: hint.whitespaceBefore, + label: hint.text, + position: typeConverters.Position.fromLocation(hint.position), + kind: fromProtocolInlayHintKind(hint.kind), + paddingLeft: hint.whitespaceBefore, + paddingRight: hint.whitespaceAfter, } }) } @@ -165,9 +72,21 @@ export default class TypeScriptInlayHintsProvider implements Disposable { function fromProtocolInlayHintKind(kind: Proto.InlayHintKind): InlayHintKind { switch (kind) { - case 'Parameter': return InlayHintKind.Parameter - case 'Type': return InlayHintKind.Type - case 'Enum': return InlayHintKind.Other - default: return InlayHintKind.Other + case 'Parameter': return 2 + case 'Type': return 1 + case 'Enum': return undefined + default: return undefined } } + +function areInlayHintsEnabledForFile(language: LanguageDescription, document: TextDocument) { + const config = workspace.getConfiguration(language.id, document.uri) + const preferences = getInlayHintsPreferences(config) + return preferences.includeInlayParameterNameHints === 'literals' || + preferences.includeInlayParameterNameHints === 'all' || + preferences.includeInlayEnumMemberValueHints || + preferences.includeInlayFunctionLikeReturnTypeHints || + preferences.includeInlayFunctionParameterTypeHints || + preferences.includeInlayPropertyDeclarationTypeHints || + preferences.includeInlayVariableTypeHints +} diff --git a/src/server/index.ts b/src/server/index.ts index d1866f6..2d42456 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -41,7 +41,7 @@ export default class TsserverService implements IServiceProvider { } }) this.selector = this.descriptions.reduce((arr, c) => { - return arr.concat(c.modeIds) + return arr.concat(c.languageIds) }, []) this.registCommands() } diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 4a58133..7560ad2 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -73,7 +73,7 @@ export default class LanguageProvider { client: TypeScriptServiceClient, typingsStatus: TypingsStatus ): void { - let languageIds = this.description.modeIds + let languageIds = this.description.languageIds let clientId = `tsc-${this.description.id}` this._register( languages.registerCompletionItemProvider(clientId, 'TSC', languageIds, @@ -167,13 +167,19 @@ export default class LanguageProvider { if (this.client.apiVersion.gte(API.v300)) { this._register(new TagClosing(this.client, this.description.id)) } - if (this.client.apiVersion.gte(API.v440) && workspace.isNvim) { - this._register(new TypeScriptInlayHintsProvider(this.client, this.fileConfigurationManager, languageIds)) + if (this.client.apiVersion.gte(API.v440)) { + if (typeof languages.registerInlayHintsProvider === 'function') { + let provider = new TypeScriptInlayHintsProvider(this.description, this.client, this.fileConfigurationManager) + this._register(provider) + this._register(languages.registerInlayHintsProvider(languageIds, provider)) + } else { + this.client.logger.error(`languages.registerInlayHintsProvider is not a function, inlay hints won't work`) + } } } public handles(resource: string, doc: TextDocument): boolean { - if (doc && this.description.modeIds.includes(doc.languageId)) { + if (doc && this.description.languageIds.includes(doc.languageId)) { return true } return this.handlesConfigFile(Uri.parse(resource)) diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index dd44d49..43e591f 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -110,7 +110,7 @@ export default class TypeScriptServiceClientHost implements Disposable { if (plugin.configNamespace && plugin.languages.length) { this.registerExtensionLanguageProvider({ id: plugin.configNamespace, - modeIds: Array.from(plugin.languages), + languageIds: Array.from(plugin.languages), diagnosticSource: 'ts-plugin', diagnosticLanguage: DiagnosticLanguage.TypeScript, diagnosticOwner: 'typescript', @@ -127,7 +127,7 @@ export default class TypeScriptServiceClientHost implements Disposable { if (languageIds.size) { this.registerExtensionLanguageProvider({ id: 'typescript-plugins', - modeIds: Array.from(languageIds.values()), + languageIds: Array.from(languageIds.values()), diagnosticSource: 'ts-plugin', diagnosticLanguage: DiagnosticLanguage.TypeScript, diagnosticOwner: 'typescript', @@ -293,7 +293,7 @@ export default class TypeScriptServiceClientHost implements Disposable { private getAllModeIds(descriptions: LanguageDescription[], pluginManager: PluginManager): string[] { const allModeIds = flatten([ - ...descriptions.map(x => x.modeIds), + ...descriptions.map(x => x.languageIds), ...pluginManager.plugins.map(x => x.languages) ]) return allModeIds diff --git a/src/server/utils/languageDescription.ts b/src/server/utils/languageDescription.ts index a796a9a..dfa6c7e 100644 --- a/src/server/utils/languageDescription.ts +++ b/src/server/utils/languageDescription.ts @@ -10,7 +10,7 @@ export interface LanguageDescription { readonly id: string readonly diagnosticSource: string readonly diagnosticLanguage: DiagnosticLanguage - readonly modeIds: string[] + readonly languageIds: string[] readonly isExternal?: boolean readonly diagnosticOwner: string readonly configFilePattern?: RegExp @@ -28,7 +28,7 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ diagnosticSource: 'ts', diagnosticOwner: 'typescript', diagnosticLanguage: DiagnosticLanguage.TypeScript, - modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact, languageModeIds.typescripttsx, languageModeIds.typescriptjsx], + languageIds: [languageModeIds.typescript, languageModeIds.typescriptreact, languageModeIds.typescripttsx, languageModeIds.typescriptjsx], configFilePattern: /^tsconfig(\..*)?\.json$/gi, standardFileExtensions: [ 'ts', @@ -41,7 +41,7 @@ export const standardLanguageDescriptions: LanguageDescription[] = [ id: 'javascript', diagnosticSource: 'ts', diagnosticOwner: 'typescript', - modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact, languageModeIds.javascriptjsx], diagnosticLanguage: DiagnosticLanguage.JavaScript, + languageIds: [languageModeIds.javascript, languageModeIds.javascriptreact, languageModeIds.javascriptjsx], diagnosticLanguage: DiagnosticLanguage.JavaScript, configFilePattern: /^jsconfig(\..*)?\.json$/gi, standardFileExtensions: [ 'js', diff --git a/yarn.lock b/yarn.lock index a37f5dc..9f95a87 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.41.tgz#81d7734c5257da9f04354bd9084a6ebbdd5198a5" integrity sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q== -coc.nvim@^0.0.81-next.23: - version "0.0.81-next.23" - resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.23.tgz#48d8238afaaa0738c6237d8c077ba1791e2f90c6" - integrity sha512-RxNx6iRz7UvdgDeMVLyNXP6xe8GU8aY0Qxl/sBI+m2RzAJDRKCPuyFU1uOSxZntLglWtzKd87k3Byymdm19uBQ== +coc.nvim@^0.0.81-next.25: + version "0.0.81-next.25" + resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.25.tgz#8f84b7c71b742e111d330fb553b0df604d4929ec" + integrity sha512-c0OOZQSjgKLGNhIpKzlxkPiPmMCmYHSVcCDNA26BqFX8X0iWt3xXqwbxKiE54zfIsz0wFqL59iBVGUSBaqHGpA== esbuild-android-arm64@0.14.11: version "0.14.11" From d5a744a725796ad46d9af8373372689f3acbbf26 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 6 May 2022 15:05:33 +0800 Subject: [PATCH 73/78] Release 1.10.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2ca9468..ddf2ec6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.10.2", + "version": "1.10.3", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 2cb095db2527ec45994d806d0b4351ce019f9600 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 13 May 2022 20:51:13 +0800 Subject: [PATCH 74/78] upgrade typescript to 4.6.4 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ddf2ec6..0c7c00d 100644 --- a/package.json +++ b/package.json @@ -920,6 +920,6 @@ "which": "^2.0.2" }, "dependencies": { - "typescript": "^4.6.3" + "typescript": "^4.6.4" } } diff --git a/yarn.lock b/yarn.lock index 9f95a87..c80582f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -145,10 +145,10 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" -typescript@^4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c" - integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw== +typescript@^4.6.4: + version "4.6.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" + integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== vscode-jsonrpc@6.0.0: version "6.0.0" From baddbc26102235870b45c87d20db19c40d43f299 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Fri, 13 May 2022 20:52:07 +0800 Subject: [PATCH 75/78] Release 1.10.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0c7c00d..7514389 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.10.3", + "version": "1.10.4", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From 68e60a6924751bbc0db8f3a88a0564a1596137b7 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 28 May 2022 06:45:25 +0800 Subject: [PATCH 76/78] upgrade typescript to 4.7.2 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 7514389..df959a0 100644 --- a/package.json +++ b/package.json @@ -920,6 +920,6 @@ "which": "^2.0.2" }, "dependencies": { - "typescript": "^4.6.4" + "typescript": "^4.7.2" } } diff --git a/yarn.lock b/yarn.lock index c80582f..75f5677 100644 --- a/yarn.lock +++ b/yarn.lock @@ -145,10 +145,10 @@ semver@^7.3.5: dependencies: lru-cache "^6.0.0" -typescript@^4.6.4: - version "4.6.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" - integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== +typescript@^4.7.2: + version "4.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4" + integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A== vscode-jsonrpc@6.0.0: version "6.0.0" From ab3f48019df63438ab9166a21962e27ae3f8e38c Mon Sep 17 00:00:00 2001 From: kevinhwang91 Date: Sat, 11 Jun 2022 16:42:25 +0800 Subject: [PATCH 77/78] fix(folding): don't join folding range (#380) --- src/server/features/folding.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/server/features/folding.ts b/src/server/features/folding.ts index 0917c41..1f257bf 100644 --- a/src/server/features/folding.ts +++ b/src/server/features/folding.ts @@ -45,16 +45,22 @@ export default class TypeScriptFoldingProvider implements FoldingRangeProvider { ): FoldingRange | undefined { const range = typeConverters.Range.fromTextSpan(span.textSpan) const kind = TypeScriptFoldingProvider.getFoldingRangeKind(span) + let { start, end } = range // Workaround for #49904 if (span.kind === 'comment') { let doc = workspace.getDocument(document.uri) - const line = doc.getline(range.start.line) + const line = doc.getline(start.line) if (line.match(/\/\/\s*#endregion/gi)) { return undefined } + } else if (span.kind === 'code') { + let doc = workspace.getDocument(document.uri) + if (end.line > start.line && /^\s*}/.test(doc.getline(end.line))) { + end.line -= 1 + end.character = doc.getline(end.line).length + } } - let { start, end } = range return FoldingRange.create(start.line, end.line, start.character, end.character, kind) } From 74103e5e35db95f60b9da8e6af3e774401946f32 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Sat, 11 Jun 2022 16:43:36 +0800 Subject: [PATCH 78/78] Release 1.10.5 --- history.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/history.md b/history.md index 0f9243d..a9a19c7 100644 --- a/history.md +++ b/history.md @@ -1,3 +1,7 @@ +# 1.10.5 + +- Fix a fold issue #380 + # 1.10.2 - Fix snippet completion not work for optional complete item. diff --git a/package.json b/package.json index df959a0..749c97c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.10.4", + "version": "1.10.5", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm",