Merge branch 'neoclide:master' into master

This commit is contained in:
サポミク 2022-06-21 02:58:20 +09:00 committed by GitHub
commit 7d076e2bf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 2237 additions and 876 deletions

View file

@ -41,7 +41,7 @@ tsserver understand your code.
installation. installation.
**Note:** tsserver could be quite slow to initialize on big project, exclude **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. **Note:** if you're using WSL, copy you project files from mounted dirs to linux home otherwise tsserver will not work properly.
@ -100,10 +100,19 @@ Almost the same as VSCode.
- Code refactor using code actions. - Code refactor using code actions.
- Find references. - Find references.
- Signature help. - Signature help.
- Call hierarchy.
- Selection range.
- Semantic tokens.
- Rename symbols support. - Rename symbols support.
- Automatic tag closing.
- Rename imports on file rename, require - Rename imports on file rename, require
[watchman](https://facebook.github.io/watchman/) installed in your \$PATH. [watchman](https://facebook.github.io/watchman/) installed in your \$PATH.
- Search for workspace symbols. - 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 module first resolved from your local workspace. If it's not found, use
tsserver from `tsserver.tsdk` configuration or use bundled tsserver with this tsserver from `tsserver.tsdk` configuration or use bundled tsserver with this
@ -117,6 +126,8 @@ for guide of coc.nvim's configuration.
- `tsserver.enable`:Enable tsserver extension, default: `true` - `tsserver.enable`:Enable tsserver extension, default: `true`
- `tsserver.locale`:Locale of tsserver, default: `""` - `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.typingsCacheLocation`:Folder path for cache typings, default: `""`
- `tsserver.formatOnType`:Run format on type special characters., default: - `tsserver.formatOnType`:Run format on type special characters., default:
`true` `true`
@ -128,10 +139,9 @@ for guide of coc.nvim's configuration.
- `tsserver.log`:Log level of tsserver, default: `"off"` - `tsserver.log`:Log level of tsserver, default: `"off"`
- `tsserver.trace.server`:Trace level of tsserver, default: `"off"` - `tsserver.trace.server`:Trace level of tsserver, default: `"off"`
- `tsserver.pluginPaths`:Folders contains tsserver plugins, default: `[]` - `tsserver.pluginPaths`:Folders contains tsserver plugins, default: `[]`
- `tsserver.debugPort`:Debug port number of tsserver
- `tsserver.watchOptions`:Configure which watching strategies should be used to - `tsserver.watchOptions`:Configure which watching strategies should be used to
keep track of files and directories. Requires using TypeScript 3.8+ in the keep track of files and directories. Requires using TypeScript 3.8+ in the
workspace, default: undefined. workspace, default: `undefined`
- `tsserver.reportStyleChecksAsWarnings` default: `true` - `tsserver.reportStyleChecksAsWarnings` default: `true`
- `tsserver.implicitProjectConfig.checkJs`:Enable checkJs for implicit project, - `tsserver.implicitProjectConfig.checkJs`:Enable checkJs for implicit project,
default: `false` default: `false`
@ -139,27 +149,38 @@ for guide of coc.nvim's configuration.
experimentalDecorators for implicit project, default: `false` experimentalDecorators for implicit project, default: `false`
- `tsserver.disableAutomaticTypeAcquisition`:Disable download of typings, - `tsserver.disableAutomaticTypeAcquisition`:Disable download of typings,
default: `false` default: `false`
- `tsserver.useBatchedBufferSync`: use batched buffer synchronize support. - `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.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 - `typescript.updateImportsOnFileMove.enable`:Enable update imports on file
move., default: `true` move., default: `true`
- `typescript.implementationsCodeLens.enable`:Enable codeLens for - `typescript.implementationsCodeLens.enable`:Enable codeLens for
implementations, default: `true` implementations, default: `true`
- `typescript.referencesCodeLens.enable`:Enable codeLens for references, - `typescript.referencesCodeLens.enable`:Enable codeLens for references,
default: `true` default: `true`
- `typescript.preferences.importModuleSpecifier` default: `"auto"` - `typescript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in typescript files. Default: `false`
- `typescript.preferences.importModuleSpecifier` default: `"shortest"`
- `typescript.preferences.importModuleSpecifierEnding` default: `"auto"` - `typescript.preferences.importModuleSpecifierEnding` default: `"auto"`
- `typescript.preferences.quoteStyle` default: `"single"` - `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 - `typescript.suggestionActions.enabled`:Enable/disable suggestion diagnostics
for TypeScript files in the editor. Requires using TypeScript 2.8 or newer in for TypeScript files in the editor. Requires using TypeScript 2.8 or newer in
the workspace., default: `true` the workspace., default: `true`
- `typescript.validate.enable`:Enable/disable TypeScript validation., default: - `typescript.validate.enable`:Enable/disable TypeScript validation., default:
`true` `true`
- `typescript.showUnused`: show unused variable hint, 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.enabled` default: `true`
- `typescript.suggest.paths`:Enable/disable suggest paths in import statement - `typescript.suggest.paths`:Enable/disable suggest paths in import statement
and require calls, default: `true` and require calls, default: `true`
- `typescript.suggest.autoImports`:Enable/disable auto import suggests., - `typescript.suggest.autoImports`:Enable/disable auto import suggests,
default: `true` default: `true`
- `typescript.suggest.completeFunctionCalls`:Enable snippet for method - `typescript.suggest.completeFunctionCalls`:Enable snippet for method
suggestion, default: `true` suggestion, default: `true`
@ -168,6 +189,13 @@ for guide of coc.nvim's configuration.
TypeScript 4.3+ in the workspace, default: `true` TypeScript 4.3+ in the workspace, default: `true`
- `typescript.suggest.includeCompletionsWithSnippetText`: Enable snippet completions - `typescript.suggest.includeCompletionsWithSnippetText`: Enable snippet completions
from TS Server. Requires using TypeScript 4.3+ in the workspace, default: `true` from TS Server. Requires using TypeScript 4.3+ in the workspace, default: `true`
- `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.suggest.includeAutomaticOptionalChainCompletions`: default: `true`
- `typescript.format.enabled`:Enable/disable format of typescript files. - `typescript.format.enabled`:Enable/disable format of typescript files.
- `typescript.format.insertSpaceAfterCommaDelimiter` default: `true` - `typescript.format.insertSpaceAfterCommaDelimiter` default: `true`
- `typescript.format.insertSpaceAfterConstructor` default: `false` - `typescript.format.insertSpaceAfterConstructor` default: `false`
@ -193,14 +221,15 @@ for guide of coc.nvim's configuration.
- `typescript.format.insertSpaceAfterTypeAssertion` default: `false` - `typescript.format.insertSpaceAfterTypeAssertion` default: `false`
- `typescript.format.placeOpenBraceOnNewLineForFunctions` default: `false` - `typescript.format.placeOpenBraceOnNewLineForFunctions` default: `false`
- `typescript.format.placeOpenBraceOnNewLineForControlBlocks` 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. - `javascript.format.enabled`: Enable/disable format for javascript files, default: `true`
- `javascript.showUnused`: show unused variable hint. - `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.updateImportsOnFileMove.enable` default: `true`
- `javascript.implementationsCodeLens.enable` default: `true` - `javascript.implementationsCodeLens.enable` default: `true`
- `javascript.referencesCodeLens.enable` default: `true` - `javascript.referencesCodeLens.enable` default: `true`
- `javascript.preferences.importModuleSpecifier` default: `"auto"` - `javascript.referencesCodeLens.showOnAllFunctions`: Enable/disable references CodeLens on all functions in JavaScript files default: `false`
- `javascript.preferences.importModuleSpecifier` default: `"shortest"`
- `javascript.preferences.importModuleSpecifierEnding` default: `"auto"` - `javascript.preferences.importModuleSpecifierEnding` default: `"auto"`
- `javascript.preferences.quoteStyle` default: `"single"` - `javascript.preferences.quoteStyle` default: `"single"`
- `javascript.validate.enable`: Enable/disable JavaScript validation., default: - `javascript.validate.enable`: Enable/disable JavaScript validation., default:
@ -219,6 +248,13 @@ for guide of coc.nvim's configuration.
- `javascript.suggest.includeCompletionsForImportStatements`: Enable/disable - `javascript.suggest.includeCompletionsForImportStatements`: Enable/disable
auto-import-style completions on partially-typed import statements. Requires auto-import-style completions on partially-typed import statements. Requires
using TypeScript 4.3+ in the workspace, default: `true` 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`
- `javascript.suggest.includeAutomaticOptionalChainCompletions`: default: `true`
- `javascript.format.insertSpaceAfterCommaDelimiter` default: `true` - `javascript.format.insertSpaceAfterCommaDelimiter` default: `true`
- `javascript.format.insertSpaceAfterConstructor` default: `false` - `javascript.format.insertSpaceAfterConstructor` default: `false`
- `javascript.format.insertSpaceAfterSemicolonInForStatements` default: `true` - `javascript.format.insertSpaceAfterSemicolonInForStatements` default: `true`
@ -243,10 +279,22 @@ for guide of coc.nvim's configuration.
- `javascript.format.insertSpaceAfterTypeAssertion` default: `false` - `javascript.format.insertSpaceAfterTypeAssertion` default: `false`
- `javascript.format.placeOpenBraceOnNewLineForFunctions` default: `false` - `javascript.format.placeOpenBraceOnNewLineForFunctions` default: `false`
- `javascript.format.placeOpenBraceOnNewLineForControlBlocks` 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`, ### Added on 1.10.0
`typescript` or `javascript` in your `coc-settings.json`.
- `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
`coc-settings.json`.
## Related extensions ## Related extensions

View file

@ -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');
});
})();

260
history.md Normal file
View file

@ -0,0 +1,260 @@
# 1.10.5
- Fix a fold issue #380
# 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.
- Add configurations `javascript.suggest.completeJSDocs` and `typescript.suggest.completeJSDocs`.
# 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.
- 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.
# 1.9.7
- Change default of `javascript.autoClosingTags` and `typescript.autoClosingTags` to `true`.
# 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

View file

@ -1,6 +1,6 @@
{ {
"name": "coc-tsserver", "name": "coc-tsserver",
"version": "1.8.6", "version": "1.10.5",
"description": "tsserver extension for coc.nvim", "description": "tsserver extension for coc.nvim",
"main": "lib/index.js", "main": "lib/index.js",
"publisher": "chemzqm", "publisher": "chemzqm",
@ -224,6 +224,12 @@
], ],
"description": "Trace level of tsserver" "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": { "tsserver.pluginPaths": {
"type": "array", "type": "array",
"default": [], "default": [],
@ -232,10 +238,6 @@
}, },
"description": "Folders contains tsserver plugins" "description": "Folders contains tsserver plugins"
}, },
"tsserver.debugPort": {
"type": "number",
"description": "Debug port number of tsserver"
},
"tsserver.reportStyleChecksAsWarnings": { "tsserver.reportStyleChecksAsWarnings": {
"type": "boolean", "type": "boolean",
"default": true "default": true
@ -260,6 +262,12 @@
"default": true, "default": true,
"description": "Use batched buffer sync support." "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": { "typescript.showUnused": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@ -285,6 +293,12 @@
"default": true, "default": true,
"description": "Enable codeLens for references" "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": { "typescript.preferences.importModuleSpecifier": {
"type": "string", "type": "string",
"default": "shortest", "default": "shortest",
@ -314,6 +328,38 @@
"description": "Preferred path ending for auto imports.", "description": "Preferred path ending for auto imports.",
"scope": "resource" "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.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": { "typescript.preferences.quoteStyle": {
"type": "string", "type": "string",
"default": "auto", "default": "auto",
@ -374,6 +420,18 @@
"description": "Enable/disable snippet completions from TS Server. Requires using TypeScript 4.3+ in the workspace.", "description": "Enable/disable snippet completions from TS Server. Requires using TypeScript 4.3+ in the workspace.",
"scope": "resource" "scope": "resource"
}, },
"typescript.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"
},
"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": { "typescript.format.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@ -471,7 +529,8 @@
}, },
"typescript.autoClosingTags": { "typescript.autoClosingTags": {
"type": "boolean", "type": "boolean",
"default": false "default": true,
"description": "Enable/disable automatic closing of JSX tags."
}, },
"javascript.showUnused": { "javascript.showUnused": {
"type": "boolean", "type": "boolean",
@ -495,6 +554,12 @@
"type": "boolean", "type": "boolean",
"default": true "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": { "javascript.preferences.importModuleSpecifier": {
"type": "string", "type": "string",
"default": "shortest", "default": "shortest",
@ -524,6 +589,22 @@
"description": "Preferred path ending for auto imports.", "description": "Preferred path ending for auto imports.",
"scope": "resource" "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": { "javascript.preferences.quoteStyle": {
"type": "string", "type": "string",
"default": "auto", "default": "auto",
@ -578,6 +659,18 @@
"description": "Enable/disable auto-import-style completions on partially-typed import statements. Requires using TypeScript 4.3+ in the workspace.", "description": "Enable/disable auto-import-style completions on partially-typed import statements. Requires using TypeScript 4.3+ in the workspace.",
"scope": "resource" "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.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": { "javascript.format.enabled": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@ -650,12 +743,117 @@
"javascript.suggest.includeAutomaticOptionalChainCompletions": { "javascript.suggest.includeAutomaticOptionalChainCompletions": {
"type": "boolean", "type": "boolean",
"default": true, "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" "scope": "resource"
}, },
"javascript.autoClosingTags": { "javascript.autoClosingTags": {
"type": "boolean", "type": "boolean",
"default": false "default": true,
"description": "Enable/disable automatic closing of JSX tags."
}, },
"javascript.format.semicolons": { "javascript.format.semicolons": {
"type": "string", "type": "string",
@ -667,6 +865,28 @@
"insert", "insert",
"remove" "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."
},
"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"
} }
} }
}, },
@ -678,20 +898,28 @@
{ {
"language": "typescriptreact", "language": "typescriptreact",
"path": "./snippets/typescript.json" "path": "./snippets/typescript.json"
},
{
"language": "javascript",
"path": "./snippets/javascript.json"
},
{
"language": "javascriptreact",
"path": "./snippets/javascript.json"
} }
] ]
}, },
"author": "chemzqm@gmail.com", "author": "chemzqm@gmail.com",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "^10.12.0", "@types/node": "^12.12.12",
"coc.nvim": "^0.0.81-next.6", "coc.nvim": "^0.0.81-next.25",
"esbuild": "^0.8.29", "esbuild": "^0.14.11",
"semver": "^7.3.2", "semver": "^7.3.5",
"vscode-languageserver-protocol": "^3.16.0", "vscode-languageserver-protocol": "^3.16.0",
"which": "^2.0.2" "which": "^2.0.2"
}, },
"dependencies": { "dependencies": {
"typescript": "^4.3.5" "typescript": "^4.7.2"
} }
} }

194
snippets/javascript.json Normal file
View file

@ -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"
}
}

View file

@ -1,7 +1,5 @@
import { commands, ExtensionContext, services, workspace } from 'coc.nvim' import { ExtensionContext, services } from 'coc.nvim'
import TsserverService from './server' import TsserverService from './server'
import { AutoFixCommand, Command, ConfigurePluginCommand, FileReferencesCommand, OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './server/commands'
import { OrganizeImportsCommand } from './server/organizeImports'
import { PluginManager } from './utils/plugins' import { PluginManager } from './utils/plugins'
interface API { interface API {
@ -9,39 +7,10 @@ interface API {
} }
export async function activate(context: ExtensionContext): Promise<API> { export async function activate(context: ExtensionContext): Promise<API> {
let { subscriptions, logger } = context let { subscriptions } = context
const config = workspace.getConfiguration().get<any>('tsserver', {})
if (!config.enable) return
const pluginManager = new PluginManager() const pluginManager = new PluginManager()
const service = new TsserverService(pluginManager) const service = new TsserverService(pluginManager, context.subscriptions)
function registCommand(cmd: Command): void { subscriptions.push(services.regist(service))
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({
id: 'tsserver.restart',
execute: (): void => {
// tslint:disable-next-line:no-floating-promises
service.stop().then(() => {
setTimeout(() => {
service.restart()
}, 100)
})
}
})
service.start().then(() => {
subscriptions.push(services.regist(service))
}, e => {
logger.error(`Error on service start:`, e)
})
return { return {
configurePlugin: (pluginId: string, configuration: {}): void => { configurePlugin: (pluginId: string, configuration: {}): void => {

View file

@ -46,7 +46,8 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider
public constructor( public constructor(
protected client: ITypeScriptServiceClient, protected client: ITypeScriptServiceClient,
private cachedResponse: CachedNavTreeResponse private cachedResponse: CachedNavTreeResponse,
protected modeId: string
) {} ) {}
public get onDidChangeCodeLenses(): Event<void> { public get onDidChangeCodeLenses(): Event<void> {
@ -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 export function getSymbolRange(
if ((item as any).nameSpan) { document: TextDocument,
return typeConverters.Range.fromTextSpan((item as any).nameSpan) 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 // In older versions, we have to calculate this manually. See #23924
const span = item.spans && item.spans[0] const span = item.spans && item.spans[0]
if (!span) { if (!span) {
return null return null
} }
const range = typeConverters.Range.fromTextSpan(span) const range = typeConverters.Range.fromTextSpan(span)
const text = document.getText(range) const text = document.getText(range)
const identifierMatch = new RegExp( const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, 'gm')
`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`, const match = identifierMatch.exec(text)
'gm' const prefixLength = match ? match.index + match[1].length : 0
) const startOffset = document.offsetAt(range.start) + prefixLength
const match = identifierMatch.exec(text) return {
const prefixLength = match ? match.index + match[1].length : 0 start: document.positionAt(startOffset),
const startOffset = document.offsetAt(range.start) + prefixLength end: document.positionAt(startOffset + item.text.length)
return {
start: document.positionAt(startOffset),
end: document.positionAt(startOffset + item.text.length)
}
} }
} }

View file

@ -345,9 +345,7 @@ export default class BufferSyncSupport {
} }
public listen(): void { public listen(): void {
if (this.listening) { if (this.listening) return
return
}
this.listening = true this.listening = true
workspace.onDidOpenTextDocument( workspace.onDidOpenTextDocument(
this.openTextDocument, this.openTextDocument,

View file

@ -251,7 +251,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
} }
const detail = details[0] const detail = details[0]
if (!item.detail && detail.displayParts.length) { if (!item.detail && detail.displayParts.length) {
item.detail = Previewer.plain(detail.displayParts) item.detail = Previewer.plainWithLinks(detail.displayParts)
} }
item.documentation = this.getDocumentation(detail) item.documentation = this.getDocumentation(detail)
const { command, additionalTextEdits } = this.getCodeActions(detail, filepath) const { command, additionalTextEdits } = this.getCodeActions(detail, filepath)
@ -259,7 +259,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
item.additionalTextEdits = additionalTextEdits item.additionalTextEdits = additionalTextEdits
if (detail && item.insertTextFormat == InsertTextFormat.Snippet) { if (detail && item.insertTextFormat == InsertTextFormat.Snippet) {
const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, position, token) const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, position, token)
if (shouldCompleteFunction) { if (shouldCompleteFunction && !item.insertText) {
this.createSnippetOfFunctionCall(item, detail) this.createSnippetOfFunctionCall(item, detail)
} }
} }
@ -354,12 +354,12 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
private getDocumentation(detail: Proto.CompletionEntryDetails): MarkupContent | undefined { private getDocumentation(detail: Proto.CompletionEntryDetails): MarkupContent | undefined {
let documentation = '' let documentation = ''
if (detail.source) { if (detail.source) {
const importPath = `'${Previewer.plain(detail.source)}'` const importPath = `'${Previewer.plainWithLinks(detail.source)}'`
const autoImportLabel = `Auto import from ${importPath}` const autoImportLabel = `Auto import from ${importPath}`
documentation += `${autoImportLabel}\n` documentation += `${autoImportLabel}\n`
} }
let parts = [ let parts = [
Previewer.plain(detail.documentation), Previewer.plainWithLinks(detail.documentation),
Previewer.tagsMarkdownPreview(detail.tags) Previewer.tagsMarkdownPreview(detail.tags)
] ]
parts = parts.filter(s => s && s.trim() != '') parts = parts.filter(s => s && s.trim() != '')

View file

@ -38,6 +38,7 @@ const getSymbolKind = (kind: string): SymbolKind => {
return SymbolKind.Variable return SymbolKind.Variable
case PConst.Kind.constructSignature: case PConst.Kind.constructSignature:
case PConst.Kind.constructorImplementation: case PConst.Kind.constructorImplementation:
return SymbolKind.Constructor
case PConst.Kind.function: case PConst.Kind.function:
case PConst.Kind.localFunction: case PConst.Kind.localFunction:
return SymbolKind.Function return SymbolKind.Function

View file

@ -40,6 +40,9 @@ export interface SuggestOptions {
readonly importStatementSuggestions: boolean readonly importStatementSuggestions: boolean
readonly includeCompletionsForImportStatements: boolean readonly includeCompletionsForImportStatements: boolean
readonly includeCompletionsWithSnippetText: boolean readonly includeCompletionsWithSnippetText: boolean
readonly includeCompletionsWithClassMemberSnippets: boolean
readonly generateReturnInDocTemplate: boolean
readonly includeCompletionsWithObjectLiteralMethodSnippets: boolean
} }
export default class FileConfigurationManager { export default class FileConfigurationManager {
@ -85,7 +88,13 @@ export default class FileConfigurationManager {
} }
public async ensureConfigurationForDocument(document: TextDocument, token: CancellationToken): Promise<void> { public async ensureConfigurationForDocument(document: TextDocument, token: CancellationToken): Promise<void> {
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) return this.ensureConfigurationOptions(document, opts.insertSpaces, opts.tabSize, token)
} }
@ -159,9 +168,12 @@ export default class FileConfigurationManager {
paths: config.get<boolean>('paths', true), paths: config.get<boolean>('paths', true),
completeFunctionCalls: config.get<boolean>('completeFunctionCalls', true), completeFunctionCalls: config.get<boolean>('completeFunctionCalls', true),
autoImports: config.get<boolean>('autoImports', true), autoImports: config.get<boolean>('autoImports', true),
includeCompletionsWithObjectLiteralMethodSnippets: config.get<boolean>('suggest.objectLiteralMethodSnippets.enabled', true),
generateReturnInDocTemplate: config.get<boolean>('jsdoc.generateReturns', true),
importStatementSuggestions: config.get<boolean>('importStatements', true), importStatementSuggestions: config.get<boolean>('importStatements', true),
includeCompletionsForImportStatements: config.get<boolean>('includeCompletionsForImportStatements', true), includeCompletionsForImportStatements: config.get<boolean>('includeCompletionsForImportStatements', true),
includeCompletionsWithSnippetText: config.get<boolean>('includeCompletionsWithSnippetText', true), includeCompletionsWithSnippetText: config.get<boolean>('includeCompletionsWithSnippetText', true),
includeCompletionsWithClassMemberSnippets: config.get<boolean>('classMemberSnippets.enabled', true),
includeAutomaticOptionalChainCompletions: config.get<boolean>('includeAutomaticOptionalChainCompletions', true) includeAutomaticOptionalChainCompletions: config.get<boolean>('includeAutomaticOptionalChainCompletions', true)
} }
} }
@ -170,17 +182,31 @@ export default class FileConfigurationManager {
if (this.client.apiVersion.lt(API.v290)) { if (this.client.apiVersion.lt(API.v290)) {
return {} 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 // getImportModuleSpecifierEndingPreference available on ts 2.9.0
const preferences: Proto.UserPreferences & { importModuleSpecifierEnding?: string } = { const preferences: Proto.UserPreferences = {
quotePreference: this.getQuoteStyle(config), quotePreference: this.getQuoteStyle(preferencesConfig),
importModuleSpecifierPreference: getImportModuleSpecifier(config) as any, importModuleSpecifierPreference: getImportModuleSpecifier(preferencesConfig) as any,
importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(config), importModuleSpecifierEnding: getImportModuleSpecifierEndingPreference(preferencesConfig),
jsxAttributeCompletionStyle: getJsxAttributeCompletionStyle(preferencesConfig),
allowTextChangesInNewFiles: uri.startsWith('file:'), allowTextChangesInNewFiles: uri.startsWith('file:'),
allowRenameOfImportPath: true, allowRenameOfImportPath: true,
providePrefixAndSuffixTextForRename: config.get<boolean>('renameShorthandProperties', true) === false ? false : config.get<boolean>('useAliasesForRenames', true), // can't support it with coc.nvim by now.
includeCompletionsForImportStatements: this.getCompleteOptions(language).includeCompletionsForImportStatements, provideRefactorNotApplicableReason: false,
includeCompletionsWithSnippetText: this.getCompleteOptions(language).includeCompletionsWithSnippetText, providePrefixAndSuffixTextForRename: preferencesConfig.get<boolean>('renameShorthandProperties', true) === false ? false : preferencesConfig.get<boolean>('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(config),
} }
return preferences return preferences
} }
@ -220,3 +246,41 @@ function getImportModuleSpecifierEndingPreference(config: WorkspaceConfiguration
default: return 'auto' default: return 'auto'
} }
} }
function getJsxAttributeCompletionStyle(config: WorkspaceConfiguration) {
switch (config.get<string>('jsxAttributeCompletionStyle')) {
case 'braces': return 'braces'
case 'none': return 'none'
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(config: WorkspaceConfiguration) {
return {
includeInlayParameterNameHints: getInlayParameterNameHintsPreference(config),
includeInlayParameterNameHintsWhenArgumentMatchesName: !config.get<boolean>(InlayHintSettingNames.parameterNamesSuppressWhenArgumentMatchesName, true),
includeInlayFunctionParameterTypeHints: config.get<boolean>(InlayHintSettingNames.parameterNamesEnabled, false),
includeInlayVariableTypeHints: config.get<boolean>(InlayHintSettingNames.variableTypesEnabled, false),
includeInlayPropertyDeclarationTypeHints: config.get<boolean>(InlayHintSettingNames.propertyDeclarationTypesEnabled, false),
includeInlayFunctionLikeReturnTypeHints: config.get<boolean>(InlayHintSettingNames.functionLikeReturnTypesEnabled, false),
includeInlayEnumMemberValueHints: config.get<boolean>(InlayHintSettingNames.enumMemberValuesEnabled, false),
} as const
}
function getInlayParameterNameHintsPreference(config: WorkspaceConfiguration) {
switch (config.get<string>('inlayHints.parameterNames.enabled')) {
case 'none': return 'none'
case 'literals': return 'literals'
case 'all': return 'all'
default: return undefined
}
}

View file

@ -45,16 +45,22 @@ export default class TypeScriptFoldingProvider implements FoldingRangeProvider {
): FoldingRange | undefined { ): FoldingRange | undefined {
const range = typeConverters.Range.fromTextSpan(span.textSpan) const range = typeConverters.Range.fromTextSpan(span.textSpan)
const kind = TypeScriptFoldingProvider.getFoldingRangeKind(span) const kind = TypeScriptFoldingProvider.getFoldingRangeKind(span)
let { start, end } = range
// Workaround for #49904 // Workaround for #49904
if (span.kind === 'comment') { if (span.kind === 'comment') {
let doc = workspace.getDocument(document.uri) 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)) { if (line.match(/\/\/\s*#endregion/gi)) {
return undefined 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) return FoldingRange.create(start.line, end.line, start.character, end.character, kind)
} }

View file

@ -7,7 +7,7 @@ import { HoverProvider } from 'coc.nvim'
import { CancellationToken, Hover, MarkedString, Position } from 'vscode-languageserver-protocol' import { CancellationToken, Hover, MarkedString, Position } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
import { tagsMarkdownPreview } from '../utils/previewer' import { markdownDocumentation } from '../utils/previewer'
import * as typeConverters from '../utils/typeConverters' import * as typeConverters from '../utils/typeConverters'
export default class TypeScriptHoverProvider implements HoverProvider { 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 private static getContents(data: Proto.QuickInfoResponseBody): MarkedString[] { // tslint:disable-line
const parts = [] const parts: MarkedString[] = []
if (data.displayString) { if (data.displayString) {
// const displayParts: string[] = []
parts.push({ language: 'typescript', value: data.displayString }) parts.push({ language: 'typescript', value: data.displayString })
} }
const markup = markdownDocumentation(data.documentation, data.tags)
const tags = tagsMarkdownPreview(data.tags) parts.push({
parts.push(data.documentation + (tags ? '\n\n' + tags : '')) language: 'markdown',
value: markup.value
})
return parts return parts
} }
} }

View file

@ -7,7 +7,7 @@ import { TextDocument } from 'coc.nvim'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import * as PConst from '../protocol.const' import * as PConst from '../protocol.const'
import * as typeConverters from '../utils/typeConverters' import * as typeConverters from '../utils/typeConverters'
import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider' import { TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider'
export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider { export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider {
public async resolveCodeLens( public async resolveCodeLens(
@ -21,43 +21,39 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
filepath, filepath,
codeLens.range.start codeLens.range.start
) )
try { const response = await this.client.execute('implementation', args, token, { lowPriority: true })
const response = await this.client.execute('implementation', args, token, { lowPriority: true }) if (response.type !== 'response' || !response.body) {
if (response && response.type == 'response' && response.body) { codeLens.command = {
const locations = response.body title: response.type === 'cancelled'
.map(reference => { ? 'cancelled'
return { : 'could not determine implementation',
uri: this.client.toResource(reference.file), command: ''
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
} }
} catch { return codeLens
// noop
}
codeLens.command = {
title: '0 implementations',
command: ''
} }
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 return codeLens
} }
@ -84,7 +80,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
): Range | null { ): Range | null {
switch (item.kind) { switch (item.kind) {
case PConst.Kind.interface: case PConst.Kind.interface:
return super.getSymbolRange(document, item) return getSymbolRange(document, item)
case PConst.Kind.class: case PConst.Kind.class:
case PConst.Kind.method: case PConst.Kind.method:
@ -92,7 +88,7 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
case PConst.Kind.memberGetAccessor: case PConst.Kind.memberGetAccessor:
case PConst.Kind.memberSetAccessor: case PConst.Kind.memberSetAccessor:
if (item.kindModifiers.match(/\babstract\b/g)) { if (item.kindModifiers.match(/\babstract\b/g)) {
return super.getSymbolRange(document, item) return getSymbolRange(document, item)
} }
break break
} }

View file

@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 default class TypeScriptInlayHintsProvider implements InlayHintsProvider {
public static readonly minVersion = API.v440
private disposables: Disposable[] = []
private readonly _onDidChangeInlayHints = new Emitter<void>()
public readonly onDidChangeInlayHints: Event<void> = this._onDidChangeInlayHints.event
constructor(
private readonly language: LanguageDescription,
private readonly client: ITypeScriptServiceClient,
private readonly fileConfigurationManager: FileConfigurationManager,
) {
let section = `${language.id}.inlayHints`
workspace.onDidChangeConfiguration(async e => {
if (e.affectsConfiguration(section)) {
this._onDidChangeInlayHints.fire()
}
}, 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)
}
public dispose(): void {
this._onDidChangeInlayHints.dispose()
disposeAll(this.disposables)
}
async provideInlayHints(document: TextDocument, range: Range, token: CancellationToken): Promise<InlayHint[]> {
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 []
}
return response.body.map(hint => {
return {
label: hint.text,
position: typeConverters.Position.fromLocation(hint.position),
kind: fromProtocolInlayHintKind(hint.kind),
paddingLeft: hint.whitespaceBefore,
paddingRight: hint.whitespaceAfter,
}
})
}
}
function fromProtocolInlayHintKind(kind: Proto.InlayHintKind): InlayHintKind {
switch (kind) {
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
}

View file

@ -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<CompletionItem[] | undefined> {
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)
}

View file

@ -1,8 +1,8 @@
import { CodeActionProvider, CodeActionProviderMetadata, commands, TextDocument, window, workspace } from 'coc.nvim'
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { CancellationToken, CodeAction, CodeActionContext, CodeActionKind, Range, WorkspaceEdit } from 'vscode-languageserver-protocol'
import { Command, registCommand } from '../commands' import { Command, registCommand } from '../commands'
import Proto from '../protocol' import Proto from '../protocol'
@ -55,13 +55,22 @@ class ApplyRefactoringCommand implements Command {
} }
private async toWorkspaceEdit(body: Proto.RefactorEditInfo): Promise<WorkspaceEdit> { private async toWorkspaceEdit(body: Proto.RefactorEditInfo): Promise<WorkspaceEdit> {
for (const edit of body.edits) {
await workspace.createFile(edit.fileName, { ignoreIfExists: true })
}
let workspaceEdit = typeConverters.WorkspaceEdit.fromFileCodeEdits( let workspaceEdit = typeConverters.WorkspaceEdit.fromFileCodeEdits(
this.client, this.client,
body.edits 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 return workspaceEdit
} }
} }

View file

@ -2,15 +2,16 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { CancellationToken, CodeLens, Range } from 'vscode-languageserver-protocol' import { CancellationToken, Position, Range } from 'vscode-languageserver-protocol'
import { TextDocument } from 'coc.nvim' import { TextDocument, workspace, CodeLens } from 'coc.nvim'
import { ExecutionTarget } from '../typescriptService'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import * as PConst from '../protocol.const' import * as PConst from '../protocol.const'
import * as typeConverters from '../utils/typeConverters' import * as typeConverters from '../utils/typeConverters'
import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider' import { TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider'
export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider { export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider {
public resolveCodeLens( public async resolveCodeLens(
codeLens: CodeLens, codeLens: CodeLens,
token: CancellationToken token: CancellationToken
): Promise<CodeLens> { ): Promise<CodeLens> {
@ -20,47 +21,35 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase
filepath, filepath,
codeLens.range.start codeLens.range.start
) )
return this.client let response = await this.client.execute('references', args, token, {
.execute('references', args, token, { lowPriority: true,
lowPriority: true executionTarget: ExecutionTarget.Semantic
}) })
.then(response => { if (!response || response.type != 'response' || !response.body) {
if (!response || response.type != 'response' || !response.body) { codeLens.command = {
throw codeLens title: response.type === 'cancelled'
} ? 'cancelled'
: 'could not determine references',
command: ''
}
return codeLens
}
const locations = response.body.refs const locations = response.body.refs
.map(reference => .filter(reference => !reference.isDefinition)
typeConverters.Location.fromTextSpan( .map(reference =>
this.client.toResource(reference.file), typeConverters.Location.fromTextSpan(
reference 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
)
)
codeLens.command = { codeLens.command = {
title: locations.length === 1 ? '1 reference' : `${locations.length} references`, title: locations.length === 1 ? '1 reference' : `${locations.length} references`,
command: locations.length ? 'editor.action.showReferences' : '', command: locations.length ? 'editor.action.showReferences' : '',
arguments: [uri, codeLens.range.start, locations] arguments: [uri, codeLens.range.start, locations]
} }
return codeLens return codeLens
})
.catch(() => {
codeLens.command = {
title: '0 references',
command: ''
}
return codeLens
})
} }
protected extractSymbol( protected extractSymbol(
@ -69,37 +58,68 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase
parent: Proto.NavigationTree | null parent: Proto.NavigationTree | null
): Range | null { ): Range | null {
if (parent && parent.kind === PConst.Kind.enum) { if (parent && parent.kind === PConst.Kind.enum) {
return super.getSymbolRange(document, item) return getSymbolRange(document, item)
} }
switch (item.kind) { switch (item.kind) {
case PConst.Kind.function: {
const showOnAllFunctions = workspace.getConfiguration(this.modeId).get<boolean>('referencesCodeLens.showOnAllFunctions')
if (showOnAllFunctions) {
return getSymbolRange(document, item)
}
}
// fallthrough
case PConst.Kind.const: case PConst.Kind.const:
case PConst.Kind.let: case PConst.Kind.let:
case PConst.Kind.variable: case PConst.Kind.variable:
case PConst.Kind.function:
// Only show references for exported variables // Only show references for exported variables
if (!item.kindModifiers.match(/\bexport\b/)) { if (/\bexport\b/.test(item.kindModifiers)) {
break return getSymbolRange(document, item)
} }
// fallthrough break
case PConst.Kind.class: case PConst.Kind.class:
if (item.text === '<class>') { if (item.text === '<class>') {
break 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.interface:
case PConst.Kind.type: case PConst.Kind.type:
case PConst.Kind.enum: 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 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
}

View file

@ -16,7 +16,7 @@ export default class TypeScriptRenameProvider implements RenameProvider {
public constructor( public constructor(
private readonly client: ITypeScriptServiceClient, private readonly client: ITypeScriptServiceClient,
private readonly fileConfigurationManager: FileConfigurationManager private readonly fileConfigurationManager: FileConfigurationManager
) { } ) {}
public async prepareRename( public async prepareRename(
document: TextDocument, document: TextDocument,
@ -60,8 +60,8 @@ export default class TypeScriptRenameProvider implements RenameProvider {
} }
if (this.client.apiVersion.gte(API.v310)) { if (this.client.apiVersion.gte(API.v310)) {
if ((renameInfo as any).fileToRename) { if (renameInfo.fileToRename) {
const edits = await this.renameFile((renameInfo as any).fileToRename, newName, token) const edits = await this.renameFile(renameInfo.fileToRename, newName, token)
if (edits) { if (edits) {
return edits return edits
} else { } else {

View file

@ -0,0 +1,294 @@
/*---------------------------------------------------------------------------------------------
* 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
}
}
private logIgnored(uri: string): void {
this.client.logger.warn(`${uri} content length exceed limit 100000`)
}
async provideDocumentSemanticTokens(document: TextDocument, token: CancellationToken): Promise<SemanticTokens | null> {
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)
}
async provideDocumentRangeSemanticTokens(document: TextDocument, range: Range, token: CancellationToken): Promise<SemanticTokens | null> {
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
}
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<SemanticTokens | null> {
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<void>((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<K extends keyof ExperimentalProtocol.ExtendedTsServerRequests>(
command: K,
args: ExperimentalProtocol.ExtendedTsServerRequests[K][0],
token: CancellationToken,
config?: ExecConfig
): Promise<ServerResponse.Response<ExperimentalProtocol.ExtendedTsServerRequests[K][1]>>
}
/**
* 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

View file

@ -60,13 +60,13 @@ export default class TypeScriptSignatureHelpProvider implements SignatureHelpPro
private convertSignature(item: Proto.SignatureHelpItem): SignatureInformation { private convertSignature(item: Proto.SignatureHelpItem): SignatureInformation {
let parameters = item.parameters.map(p => { let parameters = item.parameters.map(p => {
return { return {
label: Previewer.plain(p.displayParts), label: Previewer.plainWithLinks(p.displayParts),
documentation: Previewer.markdownDocumentation(p.documentation, []) documentation: Previewer.markdownDocumentation(p.documentation, [])
} }
}) })
let label = Previewer.plain(item.prefixDisplayParts) let label = Previewer.plainWithLinks(item.prefixDisplayParts)
label += parameters.map(parameter => parameter.label).join(Previewer.plain(item.separatorDisplayParts)) label += parameters.map(parameter => parameter.label).join(Previewer.plainWithLinks(item.separatorDisplayParts))
label += Previewer.plain(item.suffixDisplayParts) label += Previewer.plainWithLinks(item.suffixDisplayParts)
return { return {
label, label,
documentation: Previewer.markdownDocumentation( documentation: Previewer.markdownDocumentation(

View file

@ -5,13 +5,10 @@
import { CancellationTokenSource, Disposable, disposeAll, Position, Range, snippetManager, events, workspace, InsertChange } from 'coc.nvim' import { CancellationTokenSource, Disposable, disposeAll, Position, Range, snippetManager, events, workspace, InsertChange } from 'coc.nvim'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
import SnippetString from '../utils/SnippetString' import SnippetString from '../utils/SnippetString'
import * as typeConverters from '../utils/typeConverters' import * as typeConverters from '../utils/typeConverters'
export default class TagClosing implements Disposable { export default class TagClosing implements Disposable {
public static readonly minVersion = API.v300
private static _configurationLanguages: Record<string, string> = { private static _configurationLanguages: Record<string, string> = {
'javascriptreact': 'javascript', 'javascriptreact': 'javascript',
'typescriptreact': 'typescript', 'typescriptreact': 'typescript',
@ -21,27 +18,21 @@ export default class TagClosing implements Disposable {
private _disposed = false private _disposed = false
private _timeout: NodeJS.Timer | undefined = undefined private _timeout: NodeJS.Timer | undefined = undefined
private _cancel: CancellationTokenSource | undefined = undefined private _cancel: CancellationTokenSource | undefined = undefined
private lastInsert: string
constructor( constructor(
private readonly client: ITypeScriptServiceClient, private readonly client: ITypeScriptServiceClient,
private readonly descriptionLanguageId: string private readonly descriptionLanguageId: string
) { ) {
events.on('InsertCharPre', character => { events.on('TextInsert', this.onInsertChange, this, this._disposables)
this.lastInsert = character
}, null, this._disposables)
events.on('TextChangedI', this.onChange, this, this._disposables)
events.on('TextChangedP', this.onChange, this, this._disposables)
} }
private async onChange(bufnr: number, change: InsertChange): Promise<void> { private async onInsertChange(bufnr: number, change: InsertChange, lastInsert: string): Promise<void> {
let doc = workspace.getDocument((bufnr)) let doc = workspace.getDocument((bufnr))
if (!doc || !doc.attached) return if (!doc || !doc.attached) return
let enabled = this.isEnabled(doc.filetype, doc.uri) let enabled = this.isEnabled(doc.filetype, doc.uri)
if (!enabled) return if (!enabled) return
let { pre, changedtick, lnum } = change let { pre, changedtick, lnum } = change
if (!pre.endsWith('/') && !pre.endsWith('>')) return if (lastInsert !== '/' && lastInsert != '>') return
if (!pre.endsWith(this.lastInsert)) return
if (pre.length > 1 && pre[pre.length - 2] == '>') return if (pre.length > 1 && pre[pre.length - 2] == '>') return
const filepath = this.client.toOpenedFilePath(doc.uri) const filepath = this.client.toOpenedFilePath(doc.uri)
if (!filepath) return if (!filepath) return
@ -73,7 +64,6 @@ export default class TagClosing implements Disposable {
return return
} }
if (this._disposed) return if (this._disposed) return
const insertion = response.body const insertion = response.body
if (doc.changedtick === changedtick) { if (doc.changedtick === changedtick) {
snippetManager.insertSnippet( snippetManager.insertSnippet(
@ -82,7 +72,7 @@ export default class TagClosing implements Disposable {
Range.create(position, position) Range.create(position, position)
) )
} }
}, 50) }, 30)
} }
private isEnabled(languageId: string, uri: string): boolean { private isEnabled(languageId: string, uri: string): boolean {

View file

@ -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 path from 'path'
import { Disposable, Location } from 'vscode-languageserver-protocol'
import TypeScriptServiceClient from '../typescriptServiceClient' import TypeScriptServiceClient from '../typescriptServiceClient'
const countRegex = /Found\s+(\d+)\s+error/ const countRegex = /Found\s+(\d+)\s+error/
const errorRegex = /^(.+)\((\d+),(\d+)\):\s(\w+)\sTS(\d+):\s*(.+)$/ 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 { export default class WatchProject implements Disposable {
private disposables: Disposable[] = [] private disposables: Disposable[] = []
public static readonly id: string = 'tsserver.watchBuild' public static readonly id: string = 'tsserver.watchBuild'
@ -119,7 +105,7 @@ export default class WatchProject implements Disposable {
return return
} }
const tsconfigPath = workspace.getConfiguration('tsserver').get<string>('tsconfigPath', 'tsconfig.json'); const tsconfigPath = workspace.getConfiguration('tsserver').get<string>('tsconfigPath', 'tsconfig.json')
let find = await workspace.findUp([tsconfigPath]) let find = await workspace.findUp([tsconfigPath])
if (!find) { if (!find) {
window.showMessage(`${tsconfigPath} not found!`, 'error') window.showMessage(`${tsconfigPath} not found!`, 'error')

View file

@ -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 { Disposable, DocumentSelector, Emitter, Event } from 'vscode-languageserver-protocol'
import { PluginManager } from '../utils/plugins' 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 TypeScriptServiceClientHost from './typescriptServiceClientHost'
import { LanguageDescription, standardLanguageDescriptions } from './utils/languageDescription' import { LanguageDescription, standardLanguageDescriptions } from './utils/languageDescription'
@ -10,23 +12,69 @@ export default class TsserverService implements IServiceProvider {
public enable: boolean public enable: boolean
// supported language types // supported language types
public selector: DocumentSelector public selector: DocumentSelector
public state = ServiceStat.Initial public _state = ServiceStat.Initial
public clientHost: TypeScriptServiceClientHost public clientHost: TypeScriptServiceClientHost
private _onDidServiceReady = new Emitter<void>() private _onDidServiceReady = new Emitter<void>()
public readonly onServiceReady: Event<void> = this._onDidServiceReady.event public readonly onServiceReady: Event<void> = this._onDidServiceReady.event
private readonly disposables: Disposable[] = [] private readonly disposables: Disposable[] = []
private descriptions: LanguageDescription[] = [] private descriptions: LanguageDescription[] = []
constructor(private pluginManager: PluginManager) { constructor(private pluginManager: PluginManager, private readonly subscriptions: Disposable[]) {
const config = workspace.getConfiguration('tsserver') const config = workspace.getConfiguration('tsserver')
const enableJavascript = !!config.get<boolean>('enableJavascript') const enableJavascript = config.get<boolean>('enableJavascript', true)
this.enable = config.get<boolean>('enable') this.enable = config.get<boolean>('enable')
this.descriptions = standardLanguageDescriptions.filter(o => { this.descriptions = standardLanguageDescriptions.filter(o => {
return enableJavascript ? true : o.id != 'javascript' 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<boolean>('enable', true)
if (enable !== this.enable) {
if (this.enable) {
void this.start()
} else {
void this.stop()
}
}
}
})
this.selector = this.descriptions.reduce((arr, c) => { this.selector = this.descriptions.reduce((arr, c) => {
return arr.concat(c.modeIds) return arr.concat(c.languageIds)
}, []) }, [])
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 { public get config(): WorkspaceConfiguration {
@ -51,44 +99,43 @@ export default class TsserverService implements IServiceProvider {
}) })
} }
public start(): Promise<void> { public async start(): Promise<void> {
if (this.clientHost) return if (!this.enable || this._state == ServiceStat.Starting) return
this.state = ServiceStat.Starting this._state = ServiceStat.Starting
this.clientHost = new TypeScriptServiceClientHost(this.descriptions, this.pluginManager) if (this.clientHost) {
this.disposables.push(this.clientHost) let client = this.clientHost.serviceClient
client.restartTsServer()
return
}
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 let client = this.clientHost.serviceClient
return new Promise(resolve => { await new Promise(resolve => {
let started = false client.onReady(() => {
client.onTsServerStarted(() => {
Object.defineProperty(this, 'state', {
get: () => {
return this.clientHost.serviceClient.state
}
})
this._onDidServiceReady.fire(void 0) this._onDidServiceReady.fire(void 0)
if (!started) { resolve(undefined)
started = true
resolve()
}
}) })
}) })
} }
public dispose(): void {
disposeAll(this.disposables)
}
public async restart(): Promise<void> { public async restart(): Promise<void> {
if (!this.clientHost) return if (!this.enable) return
let client = this.clientHost.serviceClient await this.stop()
await client.restartTsServer() await this.start()
} }
public async stop(): Promise<void> { public async stop(): Promise<void> {
if (!this.clientHost) return if (!this.clientHost) return
this.clientHost.reset()
let client = this.clientHost.serviceClient let client = this.clientHost.serviceClient
await client.stop() await client.stop()
return this.clientHost?.dispose()
this.clientHost = null
this._state = ServiceStat.Stopped
}
public dispose(): void {
void this.stop()
this._onDidServiceReady.dispose()
disposeAll(this.disposables)
} }
} }

View file

@ -19,16 +19,19 @@ import FormattingProvider from './features/formatting'
import HoverProvider from './features/hover' import HoverProvider from './features/hover'
import ImplementationsCodeLensProvider from './features/implementationsCodeLens' import ImplementationsCodeLensProvider from './features/implementationsCodeLens'
import ImportfixProvider from './features/importFix' import ImportfixProvider from './features/importFix'
import TypeScriptInlayHintsProvider from './features/inlayHints'
import InstallModuleProvider from './features/moduleInstall' import InstallModuleProvider from './features/moduleInstall'
import QuickfixProvider from './features/quickfix' import QuickfixProvider from './features/quickfix'
import RefactorProvider from './features/refactor' import RefactorProvider from './features/refactor'
import ReferenceProvider from './features/references' import ReferenceProvider from './features/references'
import ReferencesCodeLensProvider from './features/referencesCodeLens' import ReferencesCodeLensProvider from './features/referencesCodeLens'
import RenameProvider from './features/rename' import RenameProvider from './features/rename'
import SemanticTokensProvider from './features/semanticTokens'
import SignatureHelpProvider from './features/signatureHelp' import SignatureHelpProvider from './features/signatureHelp'
import SmartSelection from './features/smartSelect' import SmartSelection from './features/smartSelect'
import TagClosing from './features/tagClosing' import TagClosing from './features/tagClosing'
import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename'
import { JsDocCompletionProvider } from './features/jsDocCompletion'
import { OrganizeImportsCodeActionProvider } from './organizeImports' import { OrganizeImportsCodeActionProvider } from './organizeImports'
import TypeScriptServiceClient from './typescriptServiceClient' import TypeScriptServiceClient from './typescriptServiceClient'
import API from './utils/api' import API from './utils/api'
@ -49,13 +52,8 @@ export default class LanguageProvider {
) { ) {
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables) workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
this.configurationChanged() this.configurationChanged()
client.onReady(() => {
let initialized = false this.registerProviders(client, typingsStatus)
client.onTsServerStarted(async () => { // tslint:disable-line
if (!initialized) {
initialized = true
this.registerProviders(client, typingsStatus)
}
}) })
} }
@ -77,14 +75,20 @@ export default class LanguageProvider {
client: TypeScriptServiceClient, client: TypeScriptServiceClient,
typingsStatus: TypingsStatus typingsStatus: TypingsStatus
): void { ): void {
let languageIds = this.description.modeIds let languageIds = this.description.languageIds
let clientId = `tsserver-${this.description.id}` let clientId = `tsc-${this.description.id}`
this._register( this._register(
languages.registerCompletionItemProvider(clientId, 'TSC', languageIds, languages.registerCompletionItemProvider(clientId, 'TSC', languageIds,
new CompletionItemProvider(client, typingsStatus, this.fileConfigurationManager, this.description.id), new CompletionItemProvider(client, typingsStatus, this.fileConfigurationManager, this.description.id),
CompletionItemProvider.triggerCharacters 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)) { if (this.client.apiVersion.gte(API.v230)) {
this._register(languages.registerCompletionItemProvider( this._register(languages.registerCompletionItemProvider(
`${this.description.id}-directive`, `${this.description.id}-directive`,
@ -107,9 +111,18 @@ export default class LanguageProvider {
this._register(languages.registerDocumentRangeFormatProvider(languageIds, formatProvider)) this._register(languages.registerDocumentRangeFormatProvider(languageIds, formatProvider))
this._register(languages.registerOnTypeFormattingEditProvider(languageIds, formatProvider, [';', '}', '\n', String.fromCharCode(27)])) this._register(languages.registerOnTypeFormattingEditProvider(languageIds, formatProvider, [';', '}', '\n', String.fromCharCode(27)]))
this._register(languages.registerCodeActionProvider(languageIds, new InstallModuleProvider(client), 'tsserver')) 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))) this._register(languages.registerCallHierarchyProvider(languageIds, new CallHierarchyProvider(client)))
} }
if (this.client.apiVersion.gte(API.v370)) {
const provider = new SemanticTokensProvider(client)
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 let { fileConfigurationManager } = this
let conf = fileConfigurationManager.getLanguageConfiguration(this.id) let conf = fileConfigurationManager.getLanguageConfiguration(this.id)
@ -145,10 +158,10 @@ export default class LanguageProvider {
'tsserver', [CodeActionKind.QuickFix])) 'tsserver', [CodeActionKind.QuickFix]))
let cachedResponse = new CachedNavTreeResponse() let cachedResponse = new CachedNavTreeResponse()
if (this.client.apiVersion.gte(API.v206) && conf.get<boolean>('referencesCodeLens.enable')) { if (this.client.apiVersion.gte(API.v206) && conf.get<boolean>('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<boolean>('implementationsCodeLens.enable')) { if (this.client.apiVersion.gte(API.v220) && conf.get<boolean>('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)) { if (this.client.apiVersion.gte(API.v350)) {
this._register(languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client))) this._register(languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client)))
@ -156,16 +169,34 @@ export default class LanguageProvider {
if (this.client.apiVersion.gte(API.v300)) { if (this.client.apiVersion.gte(API.v300)) {
this._register(new TagClosing(this.client, this.description.id)) this._register(new TagClosing(this.client, this.description.id))
} }
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 { public handles(resource: string, doc: TextDocument): boolean {
if (doc && this.description.modeIds.indexOf(doc.languageId) >= 0) { if (doc && this.description.languageIds.includes(doc.languageId)) {
return true 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)) 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 private get id(): string { // tslint:disable-line
return this.description.id return this.description.id
} }

View file

@ -19,9 +19,10 @@ export class OrganizeImportsCommand implements Command {
) { ) {
} }
private async _execute(client: TypeScriptServiceClient, document: TextDocument): Promise<WorkspaceEdit | TextEdit[] | null> { private async _execute(client: TypeScriptServiceClient, document: TextDocument, sortOnly = false): Promise<WorkspaceEdit | TextEdit[] | null> {
let file = client.toPath(document.uri) let file = client.toPath(document.uri)
const args: Proto.OrganizeImportsRequestArgs = { const args: Proto.OrganizeImportsRequestArgs = {
skipDestructiveCodeActions: sortOnly,
scope: { scope: {
type: 'file', type: 'file',
args: { args: {
@ -38,7 +39,7 @@ export class OrganizeImportsCommand implements Command {
client, client,
response.body response.body
) )
let keys = Object.keys(edit.changes) let keys = Object.keys(edit.changes || {})
if (keys.length == 1) { if (keys.length == 1) {
let doc = workspace.getDocument(keys[0]) let doc = workspace.getDocument(keys[0])
if (doc) { if (doc) {
@ -49,7 +50,7 @@ export class OrganizeImportsCommand implements Command {
if (edit) await workspace.applyEdit(edit) if (edit) await workspace.applyEdit(edit)
} }
public async execute(document?: TextDocument): Promise<void> { public async execute(document?: TextDocument, sortOnly = false): Promise<void> {
let client = await this.service.getClientHost() let client = await this.service.getClientHost()
if (!document) { if (!document) {
let doc = await workspace.document let doc = await workspace.document
@ -61,10 +62,14 @@ export class OrganizeImportsCommand implements Command {
} }
document = doc.textDocument 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 { export class OrganizeImportsCodeActionProvider implements CodeActionProvider {
// public static readonly minVersion = API.v280 // public static readonly minVersion = API.v280
@ -91,11 +96,16 @@ export class OrganizeImportsCodeActionProvider implements CodeActionProvider {
} }
await this.fileConfigManager.ensureConfigurationForDocument(document, token) await this.fileConfigManager.ensureConfigurationForDocument(document, token)
const action = CodeAction.create('Organize Imports', { const organizeImportsAction = CodeAction.create('Organize Imports', {
title: '', title: '',
command: 'tsserver.organizeImports', command: 'tsserver.organizeImports',
arguments: [document] arguments: [document]
}, CodeActionKind.SourceOrganizeImports) }, CodeActionKind.SourceOrganizeImports)
return [action] const sortImportsAction = CodeAction.create('Sort Imports', {
title: '',
command: 'tsserver.sortImports',
arguments: [document, true]
}, 'source.sortImports')
return [organizeImportsAction, sortImportsAction]
} }
} }

View file

@ -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<Proto.Response>
constructor(private childProcess: cp.ChildProcess) {
this._reader = new Reader<Proto.Response>(this.childProcess.stdout)
}
public readonly toCancelOnResourceChange = new Set<ToCancelOnResourceChanged>()
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()
}
}

View file

@ -34,7 +34,7 @@ export interface TypeScriptServerPlugin {
readonly languages: string[] readonly languages: string[]
} }
export enum ExectuionTarget { export enum ExecutionTarget {
Semantic, Semantic,
Syntax Syntax
} }
@ -43,7 +43,7 @@ export type ExecConfig = {
readonly lowPriority?: boolean readonly lowPriority?: boolean
readonly nonRecoverable?: boolean readonly nonRecoverable?: boolean
readonly cancelOnResourceChange?: string readonly cancelOnResourceChange?: string
readonly executionTarget?: ExectuionTarget readonly executionTarget?: ExecutionTarget
} }
export interface TypeScriptRequestTypes { export interface TypeScriptRequestTypes {
@ -84,6 +84,7 @@ export interface TypeScriptRequestTypes {
'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse] 'provideCallHierarchyIncomingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyIncomingCallsResponse]
'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse] 'provideCallHierarchyOutgoingCalls': [Proto.FileLocationRequestArgs, Proto.ProvideCallHierarchyOutgoingCallsResponse]
'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse] 'fileReferences': [Proto.FileRequestArgs, Proto.FileReferencesResponse]
'provideInlayHints': [Proto.InlayHintsRequestArgs, Proto.InlayHintsResponse]
} }
export interface ITypeScriptServiceClient { export interface ITypeScriptServiceClient {

View file

@ -2,13 +2,12 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { Document, ServiceStat, Uri, window, workspace } from 'coc.nvim'
import fs from 'fs' import fs from 'fs'
import os from 'os' import os from 'os'
import path from 'path' import path from 'path'
import { CancellationToken, CancellationTokenSource, Disposable, Emitter, Event } from 'vscode-languageserver-protocol' 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 { PluginManager } from '../utils/plugins'
import { CallbackMap } from './callbackMap' import { CallbackMap } from './callbackMap'
import BufferSyncSupport from './features/bufferSyncSupport' import BufferSyncSupport from './features/bufferSyncSupport'
@ -20,50 +19,12 @@ import { ExecConfig, ITypeScriptServiceClient, ServerResponse } from './typescri
import API from './utils/api' import API from './utils/api'
import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration' import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration'
import Logger from './utils/logger' 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 Tracer from './utils/tracer'
import { inferredProjectConfig } from './utils/tsconfig' import { inferredProjectConfig } from './utils/tsconfig'
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider' import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'
import VersionStatus from './utils/versionStatus' import VersionStatus from './utils/versionStatus'
import { ICallback, Reader } from './utils/wireProtocol' import ForkedTsServerProcess, { ToCancelOnResourceChanged } from './tsServerProcess'
interface ToCancelOnResourceChanged {
readonly resource: string
cancel(): void
}
class ForkedTsServerProcess {
constructor(private childProcess: cp.ChildProcess) {}
public readonly toCancelOnResourceChange = new Set<ToCancelOnResourceChanged>()
public onError(cb: (err: Error) => void): void {
this.childProcess.on('error', cb)
}
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 createReader(
callback: ICallback<Proto.Response>,
onError: (error: any) => void
): void {
// tslint:disable-next-line:no-unused-expression
new Reader<Proto.Response>(this.childProcess.stdout, callback, onError)
}
public kill(): void {
this.childProcess.kill()
}
}
export interface TsDiagnostics { export interface TsDiagnostics {
readonly kind: DiagnosticKind readonly kind: DiagnosticKind
@ -72,6 +33,8 @@ export interface TsDiagnostics {
} }
export default class TypeScriptServiceClient implements ITypeScriptServiceClient { export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
private token: number = 0
private noRestart = false
public state = ServiceStat.Initial public state = ServiceStat.Initial
public readonly logger: Logger = new Logger() public readonly logger: Logger = new Logger()
public readonly bufferSyncSupport: BufferSyncSupport public readonly bufferSyncSupport: BufferSyncSupport
@ -84,14 +47,13 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
private versionProvider: TypeScriptVersionProvider private versionProvider: TypeScriptVersionProvider
private tsServerLogFile: string | null = null private tsServerLogFile: string | null = null
private tsServerProcess: ForkedTsServerProcess | undefined private tsServerProcess: ForkedTsServerProcess | undefined
private servicePromise: Thenable<ForkedTsServerProcess> | null
private lastError: Error | null
private lastStart: number private lastStart: number
private numberRestarts: number private numberRestarts: number
private cancellationPipeName: string | null = null private cancellationPipeName: string | null = null
private _callbacks = new CallbackMap<Proto.Response>() private _callbacks = new CallbackMap<Proto.Response>()
private _requestQueue = new RequestQueue() private _requestQueue = new RequestQueue()
private _pendingResponses = new Set<number>() private _pendingResponses = new Set<number>()
private _onReady?: { promise: Promise<void>; resolve: () => void; reject: () => void }
private versionStatus: VersionStatus private versionStatus: VersionStatus
private readonly _onTsServerStarted = new Emitter<API>() private readonly _onTsServerStarted = new Emitter<API>()
@ -108,13 +70,20 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
constructor( constructor(
public readonly pluginManager: PluginManager, public readonly pluginManager: PluginManager,
public readonly modeIds: string[] public readonly modeIds: string[],
private readonly tscPathVim: string | undefined
) { ) {
this.pathSeparator = path.sep this.pathSeparator = path.sep
this.lastStart = Date.now() this.lastStart = Date.now()
this.servicePromise = null
this.lastError = null
this.numberRestarts = 0 this.numberRestarts = 0
let resolve: () => void
let reject: () => void
const p = new Promise<void>((res, rej) => {
resolve = res
reject = rej
})
this._onReady = { promise: p, resolve: resolve!, reject: reject! }
this.fileConfigurationManager = new FileConfigurationManager(this) this.fileConfigurationManager = new FileConfigurationManager(this)
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace() this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace()
this.versionProvider = new TypeScriptVersionProvider(this._configuration) this.versionProvider = new TypeScriptVersionProvider(this._configuration)
@ -130,7 +99,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
}, null, this.disposables) }, null, this.disposables)
this.bufferSyncSupport = new BufferSyncSupport(this, modeIds) this.bufferSyncSupport = new BufferSyncSupport(this, modeIds)
this.onTsServerStarted(() => { this.onReady(() => {
this.bufferSyncSupport.listen() this.bufferSyncSupport.listen()
}) })
@ -163,17 +132,20 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
return this._configuration return this._configuration
} }
public onReady(f: () => void): Promise<void> {
return this._onReady!.promise.then(f)
}
public dispose(): void { public dispose(): void {
if (this.servicePromise) { this.tsServerProcess.kill()
this.servicePromise this.diagnosticsManager.dispose()
.then(childProcess => {
childProcess.kill()
})
.then(undefined, () => void 0)
}
this.bufferSyncSupport.dispose() this.bufferSyncSupport.dispose()
this.logger.dispose() this.logger.dispose()
this._onTsServerStarted.dispose() this._onTsServerStarted.dispose()
this._onProjectLanguageServiceStateChanged.dispose()
this._onDidBeginInstallTypings.dispose()
this._onDidEndInstallTypings.dispose()
this._onTypesInstallerInitializationFailed.dispose()
this._onResendModelsRequested.dispose() this._onResendModelsRequested.dispose()
this.versionStatus.dispose() this.versionStatus.dispose()
} }
@ -186,40 +158,29 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
this.logger.error(message, data) this.logger.error(message, data)
} }
public restartTsServer(): Promise<any> { public restartTsServer(): void {
const start = () => { if (this.tsServerProcess) {
this.servicePromise = this.startService(true) this.state = ServiceStat.Stopping
return this.servicePromise this.info('Killing TS Server')
} this.isRestarting = true
this.tsServerProcess.kill()
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())
} }
this.startService(true)
} }
public stop(): Promise<void> { public stop(): Promise<void> {
if (!this.servicePromise) return return new Promise(resolve => {
return new Promise((resolve, reject) => { let { tsServerProcess } = this
this.servicePromise.then(childProcess => { if (tsServerProcess && this.state == ServiceStat.Running) {
if (this.state == ServiceStat.Running) { this.info('Killing TS Server')
this.info('Killing TS Server') tsServerProcess.onExit(() => {
childProcess.onExit(() => {
resolve()
})
childProcess.kill()
this.servicePromise = null
} else {
resolve() resolve()
} })
}, reject) this.noRestart = true
tsServerProcess.kill()
} else {
resolve()
}
}) })
} }
@ -253,125 +214,101 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
return this._tscPath return this._tscPath
} }
private service(): Thenable<ForkedTsServerProcess> {
if (this.servicePromise) {
return this.servicePromise
}
if (this.lastError) {
return Promise.reject<ForkedTsServerProcess>(this.lastError)
}
return this.startService().then(() => {
if (this.servicePromise) {
return this.servicePromise
}
})
}
public ensureServiceStarted(): void { public ensureServiceStarted(): void {
if (!this.servicePromise) { if (!this.tsServerProcess) {
this.startService().catch(err => { this.startService()
window.showMessage(`TSServer start failed: ${err.message}`, 'error')
this.error(`Service start failed: ${err.stack}`)
})
} }
} }
private async startService(resendModels = false): Promise<ForkedTsServerProcess> { private startService(resendModels = false): ForkedTsServerProcess | undefined {
const { ignoreLocalTsserver } = this.configuration const { ignoreLocalTsserver } = this.configuration
let currentVersion: TypeScriptVersion let currentVersion: TypeScriptVersion
if (!ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion() if (this.tscPathVim) currentVersion = this.versionProvider.getVersionFromTscPath(this.tscPathVim)
if (!currentVersion && !ignoreLocalTsserver) currentVersion = this.versionProvider.getLocalVersion()
if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) { if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) {
this.info('Local tsserver not found, using bundled tsserver with coc-tsserver.')
currentVersion = this.versionProvider.getDefaultVersion() currentVersion = this.versionProvider.getDefaultVersion()
} }
if (!currentVersion || !currentVersion.isValid) { if (!currentVersion || !currentVersion.isValid) {
if (this.configuration.globalTsdk) { 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 { } 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 return
} }
this._apiVersion = currentVersion.version this._apiVersion = currentVersion.version
this._tscPath = currentVersion.tscPath this._tscPath = currentVersion.tscPath
workspace.nvim.setVar('Tsserver_path', this._tscPath, true)
this.versionStatus.onDidChangeTypeScriptVersion(currentVersion) this.versionStatus.onDidChangeTypeScriptVersion(currentVersion)
this.lastError = null const tsServerForkArgs = this.getTsServerArgs(currentVersion)
const tsServerForkArgs = await this.getTsServerArgs(currentVersion) const options = { execArgv: this.getExecArgv() }
const debugPort = this._configuration.debugPort return this.startProcess(currentVersion, tsServerForkArgs, options, resendModels)
const maxTsServerMemory = this._configuration.maxTsServerMemory
const options = {
execArgv: [
...(debugPort ? [`--inspect=${debugPort}`] : []), // [`--debug-brk=5859`]
...(maxTsServerMemory ? [`--max-old-space-size=${maxTsServerMemory}`] : []),
],
cwd: workspace.root
}
this.servicePromise = this.startProcess(currentVersion, tsServerForkArgs, options, resendModels)
return this.servicePromise
} }
private startProcess(currentVersion: TypeScriptVersion, args: string[], options: IForkOptions, resendModels: boolean): Promise<ForkedTsServerProcess> { 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): ForkedTsServerProcess {
const myToken = ++this.token
this.state = ServiceStat.Starting this.state = ServiceStat.Starting
return new Promise((resolve, reject) => { try {
try { let childProcess = fork(currentVersion.tsServerPath, args, options, this.logger)
fork( this.state = ServiceStat.Running
currentVersion.tsServerPath, this.info('Starting TSServer', JSON.stringify(currentVersion, null, 2))
args, const handle = new ForkedTsServerProcess(childProcess)
options, this.tsServerProcess = handle
this.logger, this.lastStart = Date.now()
(err: any, childProcess: cp.ChildProcess | null) => { handle.onError((err: Error) => {
if (err || !childProcess) { if (this.token != myToken) return
this.state = ServiceStat.StartFailed window.showErrorMessage(`TypeScript language server exited with error. Error message is: ${err.message}`)
this.lastError = err this.error('TSServer errored with error.', err)
this.error('Starting TSServer failed with error.', err.stack) this.error(`TSServer log file: ${this.tsServerLogFile || ''}`)
return window.showMessage(`TSServer errored with error. ${err.message}`, 'error')
} this.serviceExited(false)
this.state = ServiceStat.Running })
this.info('Started TSServer', JSON.stringify(currentVersion, null, 2)) handle.onExit((code: any, signal: string) => {
const handle = new ForkedTsServerProcess(childProcess) handle.dispose()
this.tsServerProcess = handle if (this.token != myToken) return
this.lastStart = Date.now() if (code == null) {
this.info(`TSServer exited. Signal: ${signal}`)
handle.onError((err: Error) => { } else {
this.lastError = err this.error(`TSServer exited with code: ${code}. Signal: ${signal}`)
this.error('TSServer errored with error.', err) }
this.error(`TSServer log file: ${this.tsServerLogFile || ''}`) this.info(`TSServer log file: ${this.tsServerLogFile || ''}`)
window.showMessage(`TSServer errored with error. ${err.message}`, 'error') this.serviceExited(!this.isRestarting)
this.serviceExited(false) this.isRestarting = false
}) })
handle.onExit((code: any) => { handle.onData(msg => {
if (code == null) { this.dispatchMessage(msg)
this.info('TSServer normal exit') })
} else { this.serviceStarted(resendModels)
this.error(`TSServer exited with code: ${code}`) this._onReady!.resolve()
} this._onTsServerStarted.fire(currentVersion.version)
this.info(`TSServer log file: ${this.tsServerLogFile || ''}`) return handle
this.serviceExited(!this.isRestarting) } catch (err) {
this.isRestarting = false this.state = ServiceStat.StartFailed
}) this.error('Starting TSServer failed with error.', err.stack)
return undefined
handle.createReader( }
msg => {
this.dispatchMessage(msg)
},
error => {
this.error('ReaderError', error)
}
)
resolve(handle)
this.serviceStarted(resendModels)
this._onTsServerStarted.fire(currentVersion.version)
}
)
} catch (e) {
reject(e)
}
})
} }
public async openTsServerLogFile(): Promise<boolean> { public async openTsServerLogFile(): Promise<boolean> {
const isRoot = process.getuid && process.getuid() == 0 const isRoot = process.getuid && process.getuid() == 0
let echoErr = (msg: string) => { let echoErr = (msg: string) => {
window.showMessage(msg, 'error') window.showErrorMessage(msg)
} }
if (isRoot) { if (isRoot) {
echoErr('Log disabled for root user.') echoErr('Log disabled for root user.')
@ -408,6 +345,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
preferences: { preferences: {
providePrefixAndSuffixTextForRename: true, providePrefixAndSuffixTextForRename: true,
allowRenameOfImportPath: true, allowRenameOfImportPath: true,
includePackageJsonAutoImports: this._configuration.includePackageJsonAutoImports
}, },
watchOptions watchOptions
} }
@ -448,12 +386,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
private serviceExited(restart: boolean): void { private serviceExited(restart: boolean): void {
this.state = ServiceStat.Stopped this.state = ServiceStat.Stopped
this.servicePromise = null
this.tsServerLogFile = null this.tsServerLogFile = null
this._callbacks.destroy('Service died.') this._callbacks.destroy('Service died.')
this._callbacks = new CallbackMap<Proto.Response>() this._callbacks = new CallbackMap<Proto.Response>()
this._requestQueue = new RequestQueue() this._requestQueue = new RequestQueue()
this._pendingResponses = new Set<number>() this._pendingResponses = new Set<number>()
if (this.noRestart) {
this.noRestart = false
return
}
if (restart) { if (restart) {
const diff = Date.now() - this.lastStart const diff = Date.now() - this.lastStart
this.numberRestarts++ this.numberRestarts++
@ -470,7 +411,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
} }
} }
if (startService) { if (startService) {
this.startService(true) // tslint:disable-line this.startService(true)
} }
} }
} }
@ -482,7 +423,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
public toOpenedFilePath(uri: string, options: { suppressAlertOnFailure?: boolean } = {}): string | undefined { public toOpenedFilePath(uri: string, options: { suppressAlertOnFailure?: boolean } = {}): string | undefined {
if (!this.bufferSyncSupport.ensureHasBuffer(uri)) { if (!this.bufferSyncSupport.ensureHasBuffer(uri)) {
if (!options.suppressAlertOnFailure) { if (!options.suppressAlertOnFailure) {
console.error(`Unexpected resource ${uri}`) this.error(`Unexpected resource ${uri}`)
} }
return undefined return undefined
} }
@ -490,6 +431,9 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
} }
public toResource(filepath: string): string { public toResource(filepath: string): string {
if (filepath.includes('zipfile:')) {
return filepath.replace(/.*zipfile:/, 'zipfile://');
}
if (this._apiVersion.gte(API.v213)) { if (this._apiVersion.gte(API.v213)) {
if (filepath.startsWith(this.inMemoryResourcePrefix + 'untitled:')) { if (filepath.startsWith(this.inMemoryResourcePrefix + 'untitled:')) {
let resource = Uri.parse(filepath) let resource = Uri.parse(filepath)
@ -599,13 +543,14 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
} }
private fatalError(command: string, error: any): void { 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) { if (this.state === ServiceStat.Running) {
this.info('Killing TS Server by fatal error:', error) this.info('Killing TS Server by fatal error:', error)
this.service().then(service => { let { tsServerProcess } = this
service.kill() if (tsServerProcess) {
}) this.tsServerProcess = undefined
tsServerProcess.kill()
}
} }
} }
@ -630,7 +575,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: false, lowPriority?: boolean }): undefined
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>> private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>>
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>> | undefined { private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>> | undefined {
if (this.servicePromise == null) { if (!this.tsServerProcess) {
return Promise.resolve(undefined) return Promise.resolve(undefined)
} }
this.bufferSyncSupport.beforeCommand(command) this.bufferSyncSupport.beforeCommand(command)
@ -678,16 +623,15 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
if (requestItem.expectsResponse && !requestItem.isAsync) { if (requestItem.expectsResponse && !requestItem.isAsync) {
this._pendingResponses.add(requestItem.request.seq) this._pendingResponses.add(requestItem.request.seq)
} }
this.service().then(childProcess => { if (!this.tsServerProcess) return
try { try {
childProcess.write(serverRequest) this.tsServerProcess.write(serverRequest)
} catch (err) { } catch (err) {
const callback = this.fetchCallback(serverRequest.seq) const callback = this.fetchCallback(serverRequest.seq)
if (callback) { if (callback) {
callback.onError(err) callback.onError(err)
}
} }
}) }
} }
private tryCancelRequest(seq: number, command: string): boolean { private tryCancelRequest(seq: number, command: string): boolean {
@ -722,7 +666,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
if (!callback) { if (!callback) {
return undefined return undefined
} }
this._pendingResponses.delete(seq) this._pendingResponses.delete(seq)
return callback return callback
} }
@ -840,8 +783,9 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
} }
} }
private async getTsServerArgs(currentVersion: TypeScriptVersion): Promise<string[]> { private getTsServerArgs(currentVersion: TypeScriptVersion): string[] {
const args: string[] = [] const args: string[] = []
args.push('--allowLocalPluginLoads') args.push('--allowLocalPluginLoads')
if (this.apiVersion.gte(API.v250)) { if (this.apiVersion.gte(API.v250)) {
@ -859,10 +803,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
args.push('--cancellationPipeName', this.cancellationPipeName + '*') args.push('--cancellationPipeName', this.cancellationPipeName + '*')
} }
const logDir = getTempDirectory()
if (this.apiVersion.gte(API.v222)) { if (this.apiVersion.gte(API.v222)) {
const isRoot = process.getuid && process.getuid() == 0 const isRoot = process.getuid && process.getuid() == 0
if (this._configuration.tsServerLogLevel !== TsServerLogLevel.Off && !isRoot) { if (this._configuration.tsServerLogLevel !== TsServerLogLevel.Off && !isRoot) {
const logDir = getTempDirectory()
if (logDir) { if (logDir) {
this.tsServerLogFile = path.join(logDir, `tsserver.log`) this.tsServerLogFile = path.join(logDir, `tsserver.log`)
this.info('TSServer log file :', this.tsServerLogFile) this.info('TSServer log file :', this.tsServerLogFile)
@ -881,6 +825,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)) { if (this.apiVersion.gte(API.v230)) {
const pluginNames = this.pluginManager.plugins.map(x => x.name) const pluginNames = this.pluginManager.plugins.map(x => x.name)
let pluginPaths = this._configuration.tsServerPluginPaths let pluginPaths = this._configuration.tsServerPluginPaths
@ -954,11 +908,8 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
public configurePlugin(pluginName: string, configuration: {}): any { public configurePlugin(pluginName: string, configuration: {}): any {
if (this.apiVersion.gte(API.v314)) { if (this.apiVersion.gte(API.v314)) {
if (!this.servicePromise) return if (!this.tsServerProcess) return
this.servicePromise.then(() => { this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration })
// tslint:disable-next-line: no-floating-promises
this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration })
})
} }
} }
@ -1001,3 +952,15 @@ function getQueueingType(
} }
return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal 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
}

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { CancellationToken, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Disposable, Position, Range } from 'vscode-languageserver-protocol'
import { flatten } from '../utils/arrays' import { flatten } from '../utils/arrays'
import { PluginManager } from '../utils/plugins' import { PluginManager } from '../utils/plugins'
@ -22,6 +22,7 @@ import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'
const styleCheckDiagnostics = [ const styleCheckDiagnostics = [
6133, // variable is declared but never used 6133, // variable is declared but never used
6138, // property is declared but its value is never read 6138, // property is declared but its value is never read
6192, // allImportsAreUnused
7027, // unreachable code detected 7027, // unreachable code detected
7028, // unused label 7028, // unused label
7029, // fall through case in switch 7029, // fall through case in switch
@ -37,7 +38,7 @@ export default class TypeScriptServiceClientHost implements Disposable {
private readonly fileConfigurationManager: FileConfigurationManager private readonly fileConfigurationManager: FileConfigurationManager
private reportStyleCheckAsWarnings = true private reportStyleCheckAsWarnings = true
constructor(descriptions: LanguageDescription[], pluginManager: PluginManager) { constructor(descriptions: LanguageDescription[], pluginManager: PluginManager, tscPath: string | null) {
let timer: NodeJS.Timer let timer: NodeJS.Timer
const handleProjectChange = () => { const handleProjectChange = () => {
if (timer) clearTimeout(timer) if (timer) clearTimeout(timer)
@ -56,7 +57,7 @@ export default class TypeScriptServiceClientHost implements Disposable {
packageFileWatcher.onDidChange(handleProjectChange, this, this.disposables) packageFileWatcher.onDidChange(handleProjectChange, this, this.disposables)
const allModeIds = this.getAllModeIds(descriptions, pluginManager) 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.disposables.push(this.client)
this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => { this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => {
this.diagnosticsReceived(kind, resource, diagnostics).catch(e => { this.diagnosticsReceived(kind, resource, diagnostics).catch(e => {
@ -102,39 +103,43 @@ export default class TypeScriptServiceClientHost implements Disposable {
) )
this.languagePerId.set(description.id, manager) this.languagePerId.set(description.id, manager)
} }
const languageIds = new Set<string>() this.client.ensureServiceStarted()
for (const plugin of pluginManager.plugins) { this.client.onReady(() => {
if (plugin.configNamespace && plugin.languages.length) { const languageIds = new Set<string>()
for (const plugin of pluginManager.plugins) {
if (plugin.configNamespace && plugin.languages.length) {
this.registerExtensionLanguageProvider({
id: plugin.configNamespace,
languageIds: Array.from(plugin.languages),
diagnosticSource: 'ts-plugin',
diagnosticLanguage: DiagnosticLanguage.TypeScript,
diagnosticOwner: 'typescript',
standardFileExtensions: [],
isExternal: true
})
} else {
for (const language of plugin.languages) {
languageIds.add(language)
}
}
}
if (languageIds.size) {
this.registerExtensionLanguageProvider({ this.registerExtensionLanguageProvider({
id: plugin.configNamespace, id: 'typescript-plugins',
modeIds: Array.from(plugin.languages), languageIds: Array.from(languageIds.values()),
diagnosticSource: 'ts-plugin', diagnosticSource: 'ts-plugin',
diagnosticLanguage: DiagnosticLanguage.TypeScript, diagnosticLanguage: DiagnosticLanguage.TypeScript,
diagnosticOwner: 'typescript', diagnosticOwner: 'typescript',
standardFileExtensions: [],
isExternal: true 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.client.onTsServerStarted(() => {
this.triggerAllDiagnostics() this.triggerAllDiagnostics()
}) })
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables) workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
this.configurationChanged() this.configurationChanged()
} }
@ -155,10 +160,6 @@ export default class TypeScriptServiceClientHost implements Disposable {
this.ataProgressReporter.dispose() this.ataProgressReporter.dispose()
} }
public reset(): void {
this.fileConfigurationManager.reset()
}
public get serviceClient(): TypeScriptServiceClient { public get serviceClient(): TypeScriptServiceClient {
return this.client return this.client
} }
@ -174,17 +175,20 @@ export default class TypeScriptServiceClientHost implements Disposable {
return this.languagePerId.get(languageId) return this.languagePerId.get(languageId)
} }
private configurationChanged(): void { private configurationChanged(e?: ConfigurationChangeEvent): void {
const config = workspace.getConfiguration('tsserver') if (!e || e.affectsConfiguration('tsserver')) {
this.reportStyleCheckAsWarnings = config.get('reportStyleChecksAsWarnings', true) const config = workspace.getConfiguration('tsserver')
this.reportStyleCheckAsWarnings = config.get('reportStyleChecksAsWarnings', true)
}
} }
public async findLanguage(uri: string): Promise<LanguageProvider> { public async findLanguage(uri: string): Promise<LanguageProvider> {
try { try {
let doc = this.client.getDocument(uri) let doc = this.client.getDocument(uri)
if (!doc) return undefined
let languages = Array.from(this.languagePerId.values()) 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 { } catch {
return undefined return undefined
} }
@ -289,7 +293,7 @@ export default class TypeScriptServiceClientHost implements Disposable {
private getAllModeIds(descriptions: LanguageDescription[], pluginManager: PluginManager): string[] { private getAllModeIds(descriptions: LanguageDescription[], pluginManager: PluginManager): string[] {
const allModeIds = flatten([ const allModeIds = flatten([
...descriptions.map(x => x.modeIds), ...descriptions.map(x => x.languageIds),
...pluginManager.plugins.map(x => x.languages) ...pluginManager.plugins.map(x => x.languages)
]) ])
return allModeIds return allModeIds

View file

@ -35,6 +35,7 @@ export default class API {
public static readonly v340 = API.fromSimpleString('3.4.0') public static readonly v340 = API.fromSimpleString('3.4.0')
public static readonly v345 = API.fromSimpleString('3.4.5') public static readonly v345 = API.fromSimpleString('3.4.5')
public static readonly v350 = API.fromSimpleString('3.5.0') 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 v380 = API.fromSimpleString('3.8.0')
public static readonly v381 = API.fromSimpleString('3.8.1') public static readonly v381 = API.fromSimpleString('3.8.1')
public static readonly v390 = API.fromSimpleString('3.9.0') public static readonly v390 = API.fromSimpleString('3.9.0')
@ -42,6 +43,7 @@ export default class API {
public static readonly v401 = API.fromSimpleString('4.0.1') public static readonly v401 = API.fromSimpleString('4.0.1')
public static readonly v420 = API.fromSimpleString('4.2.0') public static readonly v420 = API.fromSimpleString('4.2.0')
public static readonly v430 = API.fromSimpleString('4.3.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 { public static fromVersionString(versionString: string): API {
let version = semver.valid(versionString) let version = semver.valid(versionString)

View file

@ -59,6 +59,9 @@ export function convertCompletionEntry(
insertText = label insertText = label
insertTextFormat = InsertTextFormat.Snippet insertTextFormat = InsertTextFormat.Snippet
} }
if (tsEntry.isSnippet) {
insertTextFormat = InsertTextFormat.Snippet
}
let textEdit: TextEdit | null = null let textEdit: TextEdit | null = null
if (tsEntry.replacementSpan) { if (tsEntry.replacementSpan) {
@ -73,7 +76,7 @@ export function convertCompletionEntry(
if (tsEntry.kindModifiers) { if (tsEntry.kindModifiers) {
const kindModifiers = new Set(tsEntry.kindModifiers.split(/,|\s+/g)) const kindModifiers = new Set(tsEntry.kindModifiers.split(/,|\s+/g))
if (kindModifiers.has(PConst.KindModifiers.optional)) { if (kindModifiers.has(PConst.KindModifiers.optional)) {
insertText = label insertText = insertText ?? label
label += '?' label += '?'
} }
@ -136,6 +139,7 @@ function convertKind(kind: string): CompletionItemKind {
case PConst.Kind.memberSetAccessor: case PConst.Kind.memberSetAccessor:
return CompletionItemKind.Field return CompletionItemKind.Field
case PConst.Kind.function: case PConst.Kind.function:
case PConst.Kind.localFunction:
return CompletionItemKind.Function return CompletionItemKind.Function
case PConst.Kind.method: case PConst.Kind.method:
case PConst.Kind.constructSignature: case PConst.Kind.constructSignature:

View file

@ -40,14 +40,25 @@ export namespace TsServerLogLevel {
export class TypeScriptServiceConfiguration { export class TypeScriptServiceConfiguration {
private _configuration: WorkspaceConfiguration private _configuration: WorkspaceConfiguration
private _includePackageJsonAutoImports: 'auto' | 'on' | 'off'
private constructor() { private constructor() {
this._configuration = workspace.getConfiguration('tsserver') this._configuration = workspace.getConfiguration('tsserver')
this._includePackageJsonAutoImports = workspace.getConfiguration('typescript').get<'auto' | 'on' | 'off'>('preferences.includePackageJsonAutoImports')
workspace.onDidChangeConfiguration(() => { workspace.onDidChangeConfiguration(() => {
this._configuration = workspace.getConfiguration('tsserver') this._configuration = workspace.getConfiguration('tsserver')
this._includePackageJsonAutoImports = workspace.getConfiguration('typescript').get<'auto' | 'on' | 'off'>('preferences.includePackageJsonAutoImports')
}) })
} }
public get enableTsServerTracing(): boolean {
return this._configuration.get<boolean>('enableTracing', false)
}
public get includePackageJsonAutoImports(): 'auto' | 'on' | 'off' {
return this._includePackageJsonAutoImports
}
public get locale(): string | null { public get locale(): string | null {
return this._configuration.get<string | null>('locale', null) return this._configuration.get<string | null>('locale', null)
} }
@ -97,10 +108,6 @@ export class TypeScriptServiceConfiguration {
return this._configuration.get<number>('maxTsServerMemory', 0) return this._configuration.get<number>('maxTsServerMemory', 0)
} }
public get debugPort(): number | null {
return this._configuration.get<number>('debugPort', parseInt(process.env['TSS_DEBUG'], 10))
}
public get npmLocation(): string | null { public get npmLocation(): string | null {
let path = this._configuration.get<string>('npm', '') let path = this._configuration.get<string>('npm', '')
if (path) return workspace.expand(path) if (path) return workspace.expand(path)

View file

@ -3,16 +3,18 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as languageModeIds from './languageModeIds' import * as languageModeIds from './languageModeIds'
import path from 'path'
import { Uri } from 'coc.nvim'
export interface LanguageDescription { export interface LanguageDescription {
readonly id: string readonly id: string
readonly diagnosticSource: string readonly diagnosticSource: string
readonly diagnosticLanguage: DiagnosticLanguage readonly diagnosticLanguage: DiagnosticLanguage
readonly modeIds: string[] readonly languageIds: string[]
readonly configFile?: string
readonly isExternal?: boolean readonly isExternal?: boolean
readonly diagnosticOwner: string readonly diagnosticOwner: string
readonly configFilePattern?: RegExp readonly configFilePattern?: RegExp
readonly standardFileExtensions: ReadonlyArray<string>,
} }
export const enum DiagnosticLanguage { export const enum DiagnosticLanguage {
@ -25,19 +27,45 @@ export const standardLanguageDescriptions: LanguageDescription[] = [
id: 'typescript', id: 'typescript',
diagnosticSource: 'ts', diagnosticSource: 'ts',
diagnosticOwner: 'typescript', diagnosticOwner: 'typescript',
modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact,
languageModeIds.typescripttsx, languageModeIds.typescriptjsx],
diagnosticLanguage: DiagnosticLanguage.TypeScript, diagnosticLanguage: DiagnosticLanguage.TypeScript,
configFile: 'tsconfig.json', languageIds: [languageModeIds.typescript, languageModeIds.typescriptreact, languageModeIds.typescripttsx, languageModeIds.typescriptjsx],
configFilePattern: /^tsconfig(\..*)?\.json$/gi configFilePattern: /^tsconfig(\..*)?\.json$/gi,
standardFileExtensions: [
'ts',
'tsx',
'cts',
'mts'
]
}, },
{ {
id: 'javascript', id: 'javascript',
diagnosticSource: 'ts', diagnosticSource: 'ts',
diagnosticOwner: 'typescript', diagnosticOwner: 'typescript',
modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact, languageModeIds.javascriptjsx], languageIds: [languageModeIds.javascript, languageModeIds.javascriptreact, languageModeIds.javascriptjsx], diagnosticLanguage: DiagnosticLanguage.JavaScript,
diagnosticLanguage: DiagnosticLanguage.JavaScript, configFilePattern: /^jsconfig(\..*)?\.json$/gi,
configFile: 'jsconfig.json', standardFileExtensions: [
configFilePattern: /^jsconfig(\..*)?\.json$/gi 'js',
'jsx',
'cjs',
'mjs',
'es6',
'pac',
]
} }
] ]
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)
}

View file

@ -51,12 +51,24 @@ export default class Logger {
this.logLevel('Error', message, data) 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 { public logLevel(level: string, message: string, data?: any): void {
this.output.appendLine( this.output.appendLine(
`[${level} - ${new Date().toLocaleTimeString()}] ${message}` `[${level} - ${this.now()}] ${message}`
) )
if (data) { if (data) {
this.output.appendLine(this.data2String(data)) this.output.appendLine(this.data2String(data))
} }
} }
} }
function padLeft(s: string, n: number, pad = ' ') {
return pad.repeat(Math.max(0, n - s.length)) + s
}

View file

@ -94,20 +94,15 @@ function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
return label + (text.match(/\r\n|\n/g) ? ' \n' + text : `${text}`) 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 { export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string {
return (tags || []).map(getTagDocumentation).join(' \n\n') return (tags || []).map(getTagDocumentation).join(' \n\n')
} }
export function markdownDocumentation( export function markdownDocumentation(
documentation: Proto.SymbolDisplayPart[], documentation: Proto.SymbolDisplayPart[] | string,
tags: Proto.JSDocTagInfo[] tags: Proto.JSDocTagInfo[]
): MarkupContent { ): MarkupContent {
let out = plain(documentation) let out = plainWithLinks(documentation)
const tagsPreview = tagsMarkdownPreview(tags) const tagsPreview = tagsMarkdownPreview(tags)
if (tagsPreview) { if (tagsPreview) {
out = out + ('\n\n' + 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 * Convert `@link` inline tags to markdown links
*/ */

View file

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import cp from 'child_process' import cp from 'child_process'
import net from 'net'
import os from 'os' import os from 'os'
import path from 'path' import path from 'path'
import fs from 'fs' import fs from 'fs'
@ -24,47 +23,40 @@ export function makeRandomHexString(length: number): string {
return result return result
} }
export function getTempDirectory(): string { export function getTempDirectory(): string | undefined {
let dir = path.join(os.tmpdir(), `coc.nvim-${process.pid}`) let dir = path.join(os.tmpdir(), `coc.nvim-${process.pid}`)
if (!fs.existsSync(dir)) { try {
fs.mkdirSync(dir) if (!fs.existsSync(dir)) {
fs.mkdirSync(dir)
}
} catch (e) {
return undefined
} }
return dir return dir
} }
function generatePipeName(): string { export function getTempFile(name: string): string | undefined {
return getPipeName(makeRandomHexString(40))
}
function getPipeName(name: string): string {
const fullName = 'coc-tsc-' + name
if (process.platform === 'win32') {
return '\\\\.\\pipe\\' + fullName + '-sock'
}
const tmpdir = getTempDirectory()
// Mac/Unix: use socket file
return path.join(tmpdir, fullName + '.sock')
}
export function getTempFile(name: string): string {
const fullName = 'coc-nvim-' + name const fullName = 'coc-nvim-' + name
return path.join(getTempDirectory(), fullName + '.sock') let dir = getTempDirectory()
if (!dir) return undefined
return path.join(dir, fullName + '.sock')
} }
function generatePatchedEnv( export function createTempDirectory(name: string) {
env: any, let dir = getTempDirectory()
stdInPipeName: string, if (!dir) return undefined
stdOutPipeName: string, let res = path.join(dir, name)
stdErrPipeName: string try {
): any { fs.mkdirSync(res)
} catch (e) {
return undefined
}
return res
}
function generatePatchedEnv(env: any, modulePath: string): any {
const newEnv = Object.assign({}, env) const newEnv = Object.assign({}, env)
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..')
// 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
// Ensure we always have a PATH set // Ensure we always have a PATH set
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH // tslint:disable-line newEnv['PATH'] = newEnv['PATH'] || process.env.PATH // tslint:disable-line
return newEnv return newEnv
@ -75,88 +67,14 @@ export function fork(
args: string[], args: string[],
options: IForkOptions, options: IForkOptions,
logger: Logger, logger: Logger,
callback: (error: any, cp: cp.ChildProcess | null) => void ): cp.ChildProcess {
): 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, '..', '..', '..') // tslint:disable-line
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()
}
// Create the process // Create the process
logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `) logger.info('Forking TSServer', `PATH: ${modulePath} `)
let childProcess = cp.fork(modulePath, args, {
const bootstrapperPath = path.resolve(__dirname, '../bin/tsserverForkStart')
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
silent: true, silent: true,
env: newEnv, cwd: undefined,
env: generatePatchedEnv(process.env, modulePath),
execArgv: options.execArgv execArgv: options.execArgv
}) })
return childProcess
childProcess.once('error', (err: Error) => {
closeServer()
reject(err)
})
childProcess.once('exit', (err: Error) => {
closeServer()
reject(err)
})
} }

View file

@ -6,6 +6,7 @@
* Helpers for converting FROM LanguageServer types language-server ts types * Helpers for converting FROM LanguageServer types language-server ts types
*/ */
import * as language from 'vscode-languageserver-protocol' import * as language from 'vscode-languageserver-protocol'
import { TextDocumentEdit } from 'vscode-languageserver-protocol'
import Proto from '../protocol' import Proto from '../protocol'
import * as PConst from '../protocol.const' import * as PConst from '../protocol.const'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
@ -98,14 +99,20 @@ export namespace WorkspaceEdit {
client: ITypeScriptServiceClient, client: ITypeScriptServiceClient,
edits: Iterable<Proto.FileCodeEdits> edits: Iterable<Proto.FileCodeEdits>
): language.WorkspaceEdit { ): language.WorkspaceEdit {
let changes = {} let documentChanges: TextDocumentEdit[] = []
for (const edit of edits) { for (const edit of edits) {
let uri = client.toResource(edit.fileName) let uri = client.toResource(edit.fileName)
changes[uri] = edit.textChanges.map(change => { documentChanges.push({
return TextEdit.fromCodeEdit(change) textDocument: {
uri,
version: null
},
edits: edit.textChanges.map(change => {
return TextEdit.fromCodeEdit(change)
})
}) })
} }
return { changes } return { documentChanges }
} }
} }

View file

@ -1,4 +1,4 @@
import { StatusBarItem, window } from 'coc.nvim' import { StatusBarItem, workspace, window } from 'coc.nvim'
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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() this.statusItem.hide()
if (!this._invalid) { 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<boolean>('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 this._invalid = true
} }

View file

@ -106,6 +106,15 @@ export class TypeScriptVersionProvider {
return undefined 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 { public getLocalVersion(): TypeScriptVersion | undefined {
let folders = workspace.workspaceFolders.map(f => Uri.parse(f.uri).fsPath) let folders = workspace.workspaceFolders.map(f => Uri.parse(f.uri).fsPath)
for (let p of folders) { for (let p of folders) {

View file

@ -2,7 +2,9 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 stream from 'stream'
import { Disposable, Emitter } from 'vscode-languageserver-protocol'
const DefaultSize = 8192 const DefaultSize = 8192
const ContentLength = 'Content-Length: ' const ContentLength = 'Content-Length: '
@ -99,18 +101,30 @@ export interface ICallback<T> {
(data: T): void // tslint:disable-line (data: T): void // tslint:disable-line
} }
export class Reader<T> { export class Reader<T> implements Disposable {
private readonly buffer: ProtocolBuffer = new ProtocolBuffer() private readonly buffer: ProtocolBuffer = new ProtocolBuffer()
private nextMessageLength = -1 private nextMessageLength = -1
private disposables: Disposable[] = []
public constructor( private readonly _onError = new Emitter<Error>()
private readonly readable: stream.Readable, public readonly onError = this._onError.event
private readonly callback: ICallback<T>,
private readonly onError: (error: any) => void private readonly _onData = new Emitter<T>()
) { public readonly onData = this._onData.event
this.readable.on('data', (data: Buffer) => {
public constructor(readable: stream.Readable) {
const onData = (data: Buffer) => {
this.onLengthData(data) 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 { private onLengthData(data: Buffer): void {
@ -129,10 +143,14 @@ export class Reader<T> {
} }
this.nextMessageLength = -1 this.nextMessageLength = -1
const json = JSON.parse(msg) const json = JSON.parse(msg)
this.callback(json) this._onData.fire(json)
} }
} catch (e) { } catch (e) {
this.onError(e) this._onError.fire(e)
} }
} }
public dispose(): void {
disposeAll(this.disposables)
}
} }

View file

@ -8,6 +8,7 @@
"allowUnreachableCode": true, "allowUnreachableCode": true,
"allowUnusedLabels": true, "allowUnusedLabels": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noEmit": true,
"noImplicitAny": false, "noImplicitAny": false,
"noImplicitReturns": false, "noImplicitReturns": false,
"noUnusedLocals": false, "noUnusedLocals": false,

163
yarn.lock
View file

@ -2,35 +2,153 @@
# yarn lockfile v1 # yarn lockfile v1
"@types/node@^10.12.0": "@types/node@^12.12.12":
version "10.17.44" version "12.20.41"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.44.tgz#3945e6b702cb6403f22b779c8ea9e5c3f44ead40" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.41.tgz#81d7734c5257da9f04354bd9084a6ebbdd5198a5"
integrity sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw== integrity sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q==
coc.nvim@^0.0.81-next.6: coc.nvim@^0.0.81-next.25:
version "0.0.81-next.6" version "0.0.81-next.25"
resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.6.tgz#c3ee7079a66702ebb3b06d4c2bf333d9306ec561" resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.25.tgz#8f84b7c71b742e111d330fb553b0df604d4929ec"
integrity sha512-VT+DhygyTIzu9IRrwCUljMzfNfh8TeXqqrvFsBE0E8cUwERgCAIvRbBMEDfqaaI+XFgyuwNRwbX5kEvfjG/u3g== integrity sha512-c0OOZQSjgKLGNhIpKzlxkPiPmMCmYHSVcCDNA26BqFX8X0iWt3xXqwbxKiE54zfIsz0wFqL59iBVGUSBaqHGpA==
esbuild@^0.8.29: esbuild-android-arm64@0.14.11:
version "0.8.29" version "0.14.11"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.8.29.tgz#cc20fb752e0905a3546d68ae1be58f9b97044c39" resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.11.tgz#b8b34e35a5b43880664ac7a3fbc70243d7ed894f"
integrity sha512-UDsEoeXuctVgG2hEts1Hwq2jYDGqV7nksEHEZaiCy2v+lXF5ButX4ErPAJAFi5ZNKKW+6Pom93pArV7hki6HnQ== 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: isexe@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
semver@^7.3.2: lru-cache@^6.0.0:
version "7.3.2" version "6.0.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
dependencies:
yallist "^4.0.0"
typescript@^4.3.5: semver@^7.3.5:
version "4.3.5" version "7.3.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
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: vscode-jsonrpc@6.0.0:
version "6.0.0" version "6.0.0"
@ -56,3 +174,8 @@ which@^2.0.2:
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies: dependencies:
isexe "^2.0.0" 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==