init
This commit is contained in:
commit
54d03a04c1
63 changed files with 10429 additions and 0 deletions
.gitignore.npmignoreReadme.mdpackage.json
src
index.ts
tsconfig.jsontslint.jsonyarn.lockserver
LICENSE.txtcommands.ts
features
baseCodeLensProvider.tsbufferSyncSupport.tscompletionItemProvider.tsdefinitionProvider.tsdiagnostics.tsdirectiveCommentCompletions.tsdocumentHighlight.tsdocumentSymbol.tsfileConfigurationManager.tsfolding.tsformatting.tshover.tsimplementationsCodeLens.tsorganizeImports.tsprojectError.tsquickfix.tsrefactor.tsreferences.tsreferencesCodeLens.tsrename.tsresourceMap.tssignatureHelp.tstagCompletion.tsupdatePathOnRename.tswatchBuild.tsworkspaceSymbols.ts
index.tslanguageProvider.tsprotocol.const.tsprotocol.d.tsschema.jsontypescriptService.tstypescriptServiceClient.tstypescriptServiceClientHost.tsutils
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
lib
|
4
.npmignore
Normal file
4
.npmignore
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
src
|
||||||
|
tsconfig.json
|
||||||
|
tslint.json
|
||||||
|
*.map
|
50
Readme.md
Normal file
50
Readme.md
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# coc-tsserver
|
||||||
|
|
||||||
|
Tsserver language server extension for [coc.nvim](https://github.com/neoclide/coc.nvim).
|
||||||
|
|
||||||
|
Most code from `typescript-language-features` extension which bundled with VSCode.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
In your vim/neovim, run command:
|
||||||
|
|
||||||
|
```
|
||||||
|
:CocInstall coc-tsserver
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
Almost same as VSCode.
|
||||||
|
|
||||||
|
* Support javascript & typescript and jsx/tsx.
|
||||||
|
* Install typings automatically.
|
||||||
|
* Commands to work with tsserver.
|
||||||
|
* Code completion support.
|
||||||
|
* Go to definition.
|
||||||
|
* Code validation.
|
||||||
|
* Document highlight.
|
||||||
|
* Document symbols of current buffer.
|
||||||
|
* Folding and folding range of current buffer.
|
||||||
|
* Format current buffer, range format and format on type.
|
||||||
|
* Hover for documentation.
|
||||||
|
* Implementations codeLens and references codeLens.
|
||||||
|
* Organize imports command.
|
||||||
|
* Quickfix using code actions.
|
||||||
|
* Code refactor using code actions.
|
||||||
|
* Find references.
|
||||||
|
* Signature help.
|
||||||
|
* Rename symbols support.
|
||||||
|
* Rename imports on file rename.
|
||||||
|
* Search for workspace symbols.
|
||||||
|
|
||||||
|
## Configuration options
|
||||||
|
|
||||||
|
* `tsserver.enable` set to `false` to disable tsserver language server.
|
||||||
|
* `tsserver.trace.server` trace LSP traffic in output channel.
|
||||||
|
|
||||||
|
And many more, which are same as VSCode, trigger completion in your
|
||||||
|
`coc-settings.json` to get full list.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
397
package.json
Normal file
397
package.json
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
{
|
||||||
|
"name": "coc-tsserver",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "tsserver extension for coc",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"publisher": "chemzqm",
|
||||||
|
"engines": {
|
||||||
|
"coc": "^0.0.15"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf lib",
|
||||||
|
"build": "tsc -p tsconfig.json",
|
||||||
|
"prepare": "yarn clean && yarn build"
|
||||||
|
},
|
||||||
|
"contributes": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"title": "Reload current project",
|
||||||
|
"category": "TSServer",
|
||||||
|
"command": "tsserver.reloadProjects"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Open log file of tsserver.",
|
||||||
|
"category": "TSServer",
|
||||||
|
"command": "tsserver.openTsServerLog"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Open project config file.",
|
||||||
|
"category": "TSServer",
|
||||||
|
"command": "tsserver.goToProjectConfig"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Restart tsserver.",
|
||||||
|
"category": "TSServer",
|
||||||
|
"command": "tsserver.restart"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Format current buffer.",
|
||||||
|
"category": "TSServer",
|
||||||
|
"command": "tsserver.format"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Orgnize imports of current buffer.",
|
||||||
|
"category": "TSServer",
|
||||||
|
"command": "tsserver.organizeImports"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Run `tsc --watch` for current project in terminal buffer.",
|
||||||
|
"category": "TSServer",
|
||||||
|
"command": "tsserver.watchBuild"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"configuration": {
|
||||||
|
"type": "object",
|
||||||
|
"title": "Tsserver",
|
||||||
|
"properties": {
|
||||||
|
"tsserver.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable tsserver extension"
|
||||||
|
},
|
||||||
|
"tsserver.locale": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Locale of tsserver"
|
||||||
|
},
|
||||||
|
"tsserver.typingsCacheLocation": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Folder path for cache typings"
|
||||||
|
},
|
||||||
|
"tsserver.formatOnSave": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Format document on buffer will save"
|
||||||
|
},
|
||||||
|
"tsserver.orgnizeImportOnSave": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Orgnize import on buffer will save"
|
||||||
|
},
|
||||||
|
"tsserver.formatOnType": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Run format on type special characters."
|
||||||
|
},
|
||||||
|
"tsserver.enableJavascript": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Use tsserver for javascript files"
|
||||||
|
},
|
||||||
|
"tsserver.tsdk": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Directory contains tsserver.js, works for workspace only"
|
||||||
|
},
|
||||||
|
"tsserver.npm": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Executable path of npm for download typings"
|
||||||
|
},
|
||||||
|
"tsserver.log": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "off",
|
||||||
|
"enum": [
|
||||||
|
"normal",
|
||||||
|
"terse",
|
||||||
|
"verbose",
|
||||||
|
"off"
|
||||||
|
],
|
||||||
|
"description": "Log level of tsserver"
|
||||||
|
},
|
||||||
|
"tsserver.trace.server": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "off",
|
||||||
|
"enum": [
|
||||||
|
"off",
|
||||||
|
"messages",
|
||||||
|
"verbose"
|
||||||
|
],
|
||||||
|
"description": "Trace level of tsserver"
|
||||||
|
},
|
||||||
|
"tserver.pluginNames": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Module names of tsserver plugins"
|
||||||
|
},
|
||||||
|
"tsserver.pluginRoot": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Folder contains tsserver plugins"
|
||||||
|
},
|
||||||
|
"tsserver.debugPort": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Debug port number of tsserver"
|
||||||
|
},
|
||||||
|
"tsserver.reportStyleChecksAsWarnings": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"tsserver.implicitProjectConfig.checkJs": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable checkJs for implicit project"
|
||||||
|
},
|
||||||
|
"tsserver.implicitProjectConfig.experimentalDecorators": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable experimentalDecorators for implicit project"
|
||||||
|
},
|
||||||
|
"tsserver.disableAutomaticTypeAcquisition": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Disable download of typings"
|
||||||
|
},
|
||||||
|
"typescript.updateImportsOnFileMove.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable update imports on file move."
|
||||||
|
},
|
||||||
|
"typescript.implementationsCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable codeLens for implementations"
|
||||||
|
},
|
||||||
|
"typescript.referencesCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable codeLens for references"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.useCodeSnippetsOnMethodSuggest": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable snippet for method suggestion"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.nameSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Complete for warning type of tsserver"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.autoImportSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable auto import suggestions for completion"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.commaAfterImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Add comma after import"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.moduleExports": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Include completion for module.exports"
|
||||||
|
},
|
||||||
|
"typescript.preferences.importModuleSpecifier": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "non-relative",
|
||||||
|
"enum": [
|
||||||
|
"non-relative",
|
||||||
|
"relative"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"typescript.preferences.suggestionActions.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.preferences.quoteStyle": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "single",
|
||||||
|
"enum": [
|
||||||
|
"single",
|
||||||
|
"double"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterCommaDelimiter": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterConstructor": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterSemicolonInForStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceBeforeAndAfterBinaryOperators": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterKeywordsInControlFlowStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceBeforeFunctionParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterTypeAssertion": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.placeOpenBraceOnNewLineForFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.placeOpenBraceOnNewLineForControlBlocks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.updateImportsOnFileMove.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.implementationsCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.referencesCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.useCodeSnippetsOnMethodSuggest": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.nameSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.autoImportSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.commaAfterImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.moduleExports": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Include completion for module.exports"
|
||||||
|
},
|
||||||
|
"javascript.preferences.importModuleSpecifier": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "non-relative",
|
||||||
|
"enum": [
|
||||||
|
"non-relative",
|
||||||
|
"relative"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"javascript.preferences.suggestionActions.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.quoteStyle": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "single",
|
||||||
|
"enum": [
|
||||||
|
"single",
|
||||||
|
"double"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterCommaDelimiter": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterConstructor": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterSemicolonInForStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceBeforeAndAfterBinaryOperators": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterKeywordsInControlFlowStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceBeforeFunctionParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterTypeAssertion": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.placeOpenBraceOnNewLineForFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"author": "chemzqm@gmail.com",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"@chemzqm/tsconfig": "^0.0.3",
|
||||||
|
"@chemzqm/tslint-config": "^1.0.17",
|
||||||
|
"@types/node": "^10.9.4",
|
||||||
|
"coc.nvim": "^0.0.15",
|
||||||
|
"rimraf": "^2.6.2",
|
||||||
|
"tslint": "^5.11.0",
|
||||||
|
"typescript": "^3.0.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"semver": "^5.5.1",
|
||||||
|
"tslib": "^1.9.3",
|
||||||
|
"vscode-languageserver-protocol": "^3.12.0",
|
||||||
|
"vscode-uri": "^1.0.6",
|
||||||
|
"which": "^1.3.1"
|
||||||
|
}
|
||||||
|
}
|
55
src/index.ts
Normal file
55
src/index.ts
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { services, commands, languages, ExtensionContext, workspace, TextDocumentWillSaveEvent, ServiceStat } from 'coc.nvim'
|
||||||
|
import TsserverService from './server'
|
||||||
|
import { languageIds } from './server/utils/languageModeIds'
|
||||||
|
import { OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './server/commands'
|
||||||
|
import { TextEdit } from 'vscode-languageserver-types'
|
||||||
|
import { Command } from './server/commands'
|
||||||
|
|
||||||
|
export async function activate(context: ExtensionContext): Promise<void> {
|
||||||
|
let { subscriptions } = context
|
||||||
|
const config = workspace.getConfiguration().get('tsserver', {}) as any
|
||||||
|
if (!config.enable) return
|
||||||
|
const service = new TsserverService()
|
||||||
|
|
||||||
|
subscriptions.push(
|
||||||
|
(services as any).regist(service)
|
||||||
|
)
|
||||||
|
|
||||||
|
function onWillSave(event: TextDocumentWillSaveEvent): void {
|
||||||
|
if (service.state != ServiceStat.Running) return
|
||||||
|
let config = service.config
|
||||||
|
let formatOnSave = config.get<boolean>('formatOnSave')
|
||||||
|
if (!formatOnSave) return
|
||||||
|
let { languageId } = event.document
|
||||||
|
if (languageIds.indexOf(languageId) == -1) return
|
||||||
|
let willSaveWaitUntil = async (): Promise<TextEdit[]> => {
|
||||||
|
let options = await workspace.getFormatOptions(event.document.uri)
|
||||||
|
let textEdits = await languages.provideDocumentFormattingEdits(event.document, options)
|
||||||
|
return textEdits
|
||||||
|
}
|
||||||
|
event.waitUntil(willSaveWaitUntil())
|
||||||
|
}
|
||||||
|
|
||||||
|
function registCommand(cmd: Command): void {
|
||||||
|
let { id, execute } = cmd
|
||||||
|
subscriptions.push(commands.registerCommand(id as string, execute, cmd))
|
||||||
|
}
|
||||||
|
|
||||||
|
registCommand(new ReloadProjectsCommand(service.clientHost))
|
||||||
|
registCommand(new OpenTsServerLogCommand(service.clientHost))
|
||||||
|
registCommand(new TypeScriptGoToProjectConfigCommand(service.clientHost))
|
||||||
|
registCommand(commands.register({
|
||||||
|
id: 'tsserver.restart',
|
||||||
|
execute: (): void => {
|
||||||
|
service.stop().then(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
service.restart()
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
subscriptions.push(
|
||||||
|
workspace.onWillSaveUntil(onWillSave, null, 'tsserver')
|
||||||
|
)
|
||||||
|
}
|
23
src/server/LICENSE.txt
Normal file
23
src/server/LICENSE.txt
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2015 - present Microsoft Corporation
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
79
src/server/commands.ts
Normal file
79
src/server/commands.ts
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import { CancellationToken } from 'vscode-languageserver-protocol'
|
||||||
|
import URI from 'vscode-uri'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import { ProjectInfoResponse } from './protocol'
|
||||||
|
import TypeScriptServiceClientHost from './typescriptServiceClientHost'
|
||||||
|
|
||||||
|
export interface Command {
|
||||||
|
readonly id: string | string[]
|
||||||
|
execute(...args: any[]): void | Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReloadProjectsCommand implements Command {
|
||||||
|
public readonly id = 'tsserver.reloadProjects'
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly client: TypeScriptServiceClientHost
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public execute(): void {
|
||||||
|
this.client.reloadProjects()
|
||||||
|
workspace.showMessage('projects reloaded')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class OpenTsServerLogCommand implements Command {
|
||||||
|
public readonly id = 'tsserver.openTsServerLog'
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly client: TypeScriptServiceClientHost
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public execute(): void {
|
||||||
|
this.client.serviceClient.openTsServerLogFile() // tslint:disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeScriptGoToProjectConfigCommand implements Command {
|
||||||
|
public readonly id = 'tsserver.goToProjectConfig'
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly client: TypeScriptServiceClientHost
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async execute(): Promise<void> {
|
||||||
|
let doc = await workspace.document
|
||||||
|
await goToProjectConfig(this.client, doc.uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function goToProjectConfig(clientHost: TypeScriptServiceClientHost, uri: string): Promise<void> {
|
||||||
|
if (!clientHost.handles(uri)) {
|
||||||
|
workspace.showMessage('Could not determine TypeScript or JavaScript project. Unsupported file type', 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const client = clientHost.serviceClient
|
||||||
|
const file = client.toPath(uri)
|
||||||
|
let res: ProjectInfoResponse | undefined
|
||||||
|
try {
|
||||||
|
res = await client.execute('projectInfo', { file, needFileNameList: false }, CancellationToken.None)
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
if (!res || !res.body) {
|
||||||
|
workspace.showMessage('Could not determine TypeScript or JavaScript project.', 'warning')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const { configFileName } = res.body
|
||||||
|
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
|
||||||
|
await workspace.openResource(URI.file(configFileName).toString())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
workspace.showMessage('Config file not found', 'warning')
|
||||||
|
}
|
||||||
|
|
||||||
|
function isImplicitProjectConfigFile(configFileName: string): boolean {
|
||||||
|
return configFileName.indexOf('/dev/null/') === 0
|
||||||
|
}
|
152
src/server/features/baseCodeLensProvider.ts
Normal file
152
src/server/features/baseCodeLensProvider.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, CodeLens, Emitter, Event, Range, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { CodeLensProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import { escapeRegExp } from '../utils/regexp'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export class CachedNavTreeResponse {
|
||||||
|
private response?: Promise<Proto.NavTreeResponse>
|
||||||
|
private version = -1
|
||||||
|
private document = ''
|
||||||
|
|
||||||
|
public execute(document: TextDocument, f: () => Promise<Proto.NavTreeResponse>): Promise<Proto.NavTreeResponse> {
|
||||||
|
if (this.matches(document)) {
|
||||||
|
return this.response
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.update(document, f())
|
||||||
|
}
|
||||||
|
|
||||||
|
private matches(document: TextDocument): boolean {
|
||||||
|
return (
|
||||||
|
this.version === document.version &&
|
||||||
|
this.document === document.uri.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private update(
|
||||||
|
document: TextDocument,
|
||||||
|
response: Promise<Proto.NavTreeResponse>
|
||||||
|
): Promise<Proto.NavTreeResponse> {
|
||||||
|
this.response = response
|
||||||
|
this.version = document.version
|
||||||
|
this.document = document.uri.toString()
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider {
|
||||||
|
private onDidChangeCodeLensesEmitter = new Emitter<void>()
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
protected client: ITypeScriptServiceClient,
|
||||||
|
private cachedResponse: CachedNavTreeResponse
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public get onDidChangeCodeLenses(): Event<void> {
|
||||||
|
return this.onDidChangeCodeLensesEmitter.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provideCodeLenses(
|
||||||
|
document: TextDocument,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<CodeLens[]> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.cachedResponse.execute(document, () =>
|
||||||
|
this.client.execute('navtree', { file: filepath }, token)
|
||||||
|
)
|
||||||
|
if (!response) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const tree = response.body
|
||||||
|
const referenceableSpans: Range[] = []
|
||||||
|
if (tree && tree.childItems) {
|
||||||
|
tree.childItems.forEach(item =>
|
||||||
|
this.walkNavTree(document, item, null, referenceableSpans)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return referenceableSpans.map(
|
||||||
|
range => {
|
||||||
|
return {
|
||||||
|
range,
|
||||||
|
data: { uri: document.uri }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract extractSymbol(
|
||||||
|
document: TextDocument,
|
||||||
|
item: Proto.NavigationTree,
|
||||||
|
parent: Proto.NavigationTree | null
|
||||||
|
): Range | null
|
||||||
|
|
||||||
|
private walkNavTree(
|
||||||
|
document: TextDocument,
|
||||||
|
item: Proto.NavigationTree,
|
||||||
|
parent: Proto.NavigationTree | null,
|
||||||
|
results: Range[]
|
||||||
|
): void {
|
||||||
|
if (!item) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = this.extractSymbol(document, item, parent)
|
||||||
|
if (range) {
|
||||||
|
results.push(range)
|
||||||
|
}
|
||||||
|
if (item.childItems) {
|
||||||
|
item.childItems.forEach(child =>
|
||||||
|
this.walkNavTree(document, child, item, results)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
protected getSymbolRange(
|
||||||
|
document: TextDocument,
|
||||||
|
item: Proto.NavigationTree
|
||||||
|
): Range | null {
|
||||||
|
if (!item) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// TS 3.0+ provides a span for just the symbol
|
||||||
|
if ((item as any).nameSpan) {
|
||||||
|
return typeConverters.Range.fromTextSpan((item as any).nameSpan)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In older versions, we have to calculate this manually. See #23924
|
||||||
|
const span = item.spans && item.spans[0]
|
||||||
|
if (!span) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = typeConverters.Range.fromTextSpan(span)
|
||||||
|
const text = document.getText(range)
|
||||||
|
|
||||||
|
const identifierMatch = new RegExp(
|
||||||
|
`^(.*?(\\b|\\W))${escapeRegExp(item.text || '')}(\\b|\\W)`,
|
||||||
|
'gm'
|
||||||
|
)
|
||||||
|
const match = identifierMatch.exec(text)
|
||||||
|
const prefixLength = match ? match.index + match[1].length : 0
|
||||||
|
const startOffset = document.offsetAt(range.start) + prefixLength
|
||||||
|
return {
|
||||||
|
start: document.positionAt(startOffset),
|
||||||
|
end: document.positionAt(startOffset + item.text.length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
191
src/server/features/bufferSyncSupport.ts
Normal file
191
src/server/features/bufferSyncSupport.ts
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { DidChangeTextDocumentParams, Disposable, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { disposeAll, workspace } from 'coc.nvim'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import API from '../utils/api'
|
||||||
|
import { Delayer } from '../utils/async'
|
||||||
|
import * as languageModeIds from '../utils/languageModeIds'
|
||||||
|
|
||||||
|
function mode2ScriptKind(
|
||||||
|
mode: string
|
||||||
|
): 'TS' | 'TSX' | 'JS' | 'JSX' | undefined {
|
||||||
|
switch (mode) {
|
||||||
|
case languageModeIds.typescript:
|
||||||
|
return 'TS'
|
||||||
|
case languageModeIds.typescripttsx:
|
||||||
|
return 'TSX'
|
||||||
|
case languageModeIds.typescriptjsx:
|
||||||
|
return 'TSX'
|
||||||
|
case languageModeIds.typescriptreact:
|
||||||
|
return 'TSX'
|
||||||
|
case languageModeIds.javascript:
|
||||||
|
return 'JS'
|
||||||
|
case languageModeIds.javascriptreact:
|
||||||
|
return 'JSX'
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BufferSyncSupport {
|
||||||
|
private readonly client: ITypeScriptServiceClient
|
||||||
|
|
||||||
|
private _validate: boolean
|
||||||
|
private readonly modeIds: Set<string>
|
||||||
|
private readonly uris: Set<string> = new Set()
|
||||||
|
private readonly disposables: Disposable[] = []
|
||||||
|
|
||||||
|
private readonly pendingDiagnostics = new Map<string, number>()
|
||||||
|
private readonly diagnosticDelayer: Delayer<any>
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
client: ITypeScriptServiceClient,
|
||||||
|
modeIds: string[],
|
||||||
|
validate: boolean
|
||||||
|
) {
|
||||||
|
this.client = client
|
||||||
|
this.modeIds = new Set<string>(modeIds)
|
||||||
|
this._validate = validate || false
|
||||||
|
this.diagnosticDelayer = new Delayer<any>(300)
|
||||||
|
}
|
||||||
|
|
||||||
|
public listen(): void {
|
||||||
|
workspace.onDidOpenTextDocument(
|
||||||
|
this.onDidOpenTextDocument,
|
||||||
|
this,
|
||||||
|
this.disposables
|
||||||
|
)
|
||||||
|
workspace.onDidCloseTextDocument(
|
||||||
|
this.onDidCloseTextDocument,
|
||||||
|
this,
|
||||||
|
this.disposables
|
||||||
|
)
|
||||||
|
workspace.onDidChangeTextDocument(
|
||||||
|
this.onDidChangeTextDocument,
|
||||||
|
this,
|
||||||
|
this.disposables
|
||||||
|
)
|
||||||
|
workspace.textDocuments.forEach(this.onDidOpenTextDocument, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public reInitialize(): void {
|
||||||
|
workspace.textDocuments.forEach(this.onDidOpenTextDocument, this)
|
||||||
|
}
|
||||||
|
|
||||||
|
public set validate(value: boolean) {
|
||||||
|
this._validate = value
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this.pendingDiagnostics.clear()
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDidOpenTextDocument(document: TextDocument): void {
|
||||||
|
if (!this.modeIds.has(document.languageId)) return
|
||||||
|
let { uri } = document
|
||||||
|
let filepath = this.client.toPath(uri)
|
||||||
|
this.uris.add(uri)
|
||||||
|
const args: Proto.OpenRequestArgs = {
|
||||||
|
file: filepath,
|
||||||
|
fileContent: document.getText()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.client.apiVersion.gte(API.v203)) {
|
||||||
|
const scriptKind = mode2ScriptKind(document.languageId)
|
||||||
|
if (scriptKind) {
|
||||||
|
args.scriptKindName = scriptKind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.client.execute('open', args, false) // tslint:disable-line
|
||||||
|
this.requestDiagnostic(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDidCloseTextDocument(document: TextDocument): void {
|
||||||
|
let { uri } = document
|
||||||
|
if (!this.uris.has(uri)) return
|
||||||
|
let filepath = this.client.toPath(uri)
|
||||||
|
const args: Proto.FileRequestArgs = {
|
||||||
|
file: filepath
|
||||||
|
}
|
||||||
|
this.client.execute('close', args, false) // tslint:disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDidChangeTextDocument(e: DidChangeTextDocumentParams): void {
|
||||||
|
let { textDocument, contentChanges } = e
|
||||||
|
let { uri } = textDocument
|
||||||
|
if (!this.uris.has(uri)) return
|
||||||
|
let filepath = this.client.toPath(uri)
|
||||||
|
for (const { range, text } of contentChanges) {
|
||||||
|
const args: Proto.ChangeRequestArgs = {
|
||||||
|
file: filepath,
|
||||||
|
line: range ? range.start.line + 1 : 1,
|
||||||
|
offset: range ? range.start.character + 1 : 1,
|
||||||
|
endLine: range ? range.end.line + 1 : 2 ** 24,
|
||||||
|
endOffset: range ? range.end.character + 1 : 1,
|
||||||
|
insertString: text
|
||||||
|
}
|
||||||
|
this.client.execute('change', args, false) // tslint:disable-line
|
||||||
|
}
|
||||||
|
this.requestDiagnostic(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
public requestAllDiagnostics(): void {
|
||||||
|
if (!this._validate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const uri of this.uris) {
|
||||||
|
this.pendingDiagnostics.set(uri, Date.now())
|
||||||
|
}
|
||||||
|
this.diagnosticDelayer.trigger(() => { // tslint:disable-line
|
||||||
|
this.sendPendingDiagnostics()
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
public requestDiagnostic(uri: string): void {
|
||||||
|
if (!this._validate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let document = workspace.getDocument(uri)
|
||||||
|
if (!document) return
|
||||||
|
this.pendingDiagnostics.set(uri, Date.now())
|
||||||
|
let delay = 300
|
||||||
|
const lineCount = document.lineCount
|
||||||
|
delay = Math.min(Math.max(Math.ceil(lineCount / 20), 300), 800)
|
||||||
|
this.diagnosticDelayer.trigger(() => {
|
||||||
|
this.sendPendingDiagnostics()
|
||||||
|
}, delay) // tslint:disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasPendingDiagnostics(uri: string): boolean {
|
||||||
|
return this.pendingDiagnostics.has(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendPendingDiagnostics(): void {
|
||||||
|
if (!this._validate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const files = Array.from(this.pendingDiagnostics.entries())
|
||||||
|
.sort((a, b) => a[1] - b[1])
|
||||||
|
.map(entry => this.client.toPath(entry[0]))
|
||||||
|
|
||||||
|
// Add all open TS buffers to the geterr request. They might be visible
|
||||||
|
for (const uri of this.uris) {
|
||||||
|
if (!this.pendingDiagnostics.get(uri)) {
|
||||||
|
let file = this.client.toPath(uri)
|
||||||
|
files.push(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (files.length) {
|
||||||
|
const args: Proto.GeterrRequestArgs = {
|
||||||
|
delay: 0,
|
||||||
|
files
|
||||||
|
}
|
||||||
|
this.client.execute('geterr', args, false) // tslint:disable-line
|
||||||
|
}
|
||||||
|
this.pendingDiagnostics.clear()
|
||||||
|
}
|
||||||
|
}
|
375
src/server/features/completionItemProvider.ts
Normal file
375
src/server/features/completionItemProvider.ts
Normal file
|
@ -0,0 +1,375 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Command, CompletionContext, CompletionItem, InsertTextFormat, MarkupContent, MarkupKind, Position, TextDocument, TextEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import { commands, workspace } from 'coc.nvim'
|
||||||
|
import { CompletionItemProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import * as PConst from '../protocol.const'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import API from '../utils/api'
|
||||||
|
import { applyCodeAction } from '../utils/codeAction'
|
||||||
|
import { convertCompletionEntry, resolveItem } from '../utils/completionItem'
|
||||||
|
import * as Previewer from '../utils/previewer'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
import TypingsStatus from '../utils/typingsStatus'
|
||||||
|
import FileConfigurationManager, { CompletionOptions } from './fileConfigurationManager'
|
||||||
|
|
||||||
|
// command center
|
||||||
|
export interface CommandItem {
|
||||||
|
readonly id: string | string[]
|
||||||
|
execute(...args: any[]): void | Promise<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApplyCompletionCodeActionCommand implements CommandItem {
|
||||||
|
public static readonly ID = '_typescript.applyCompletionCodeAction'
|
||||||
|
public readonly id = ApplyCompletionCodeActionCommand.ID
|
||||||
|
public constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply code action on complete
|
||||||
|
public async execute(codeActions: Proto.CodeAction[]): Promise<void> {
|
||||||
|
if (codeActions.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (codeActions.length === 1) {
|
||||||
|
await applyCodeAction(this.client, codeActions[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const idx = await workspace.showQuickpick(codeActions.map(o => o.description), 'Select code action to apply')
|
||||||
|
if (idx < 0) return
|
||||||
|
const action = codeActions[idx]
|
||||||
|
await applyCodeAction(this.client, action)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TypeScriptCompletionItemProvider implements CompletionItemProvider {
|
||||||
|
|
||||||
|
public static readonly triggerCharacters = ['.', '@', '<']
|
||||||
|
private completeOption: CompletionOptions
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
private readonly typingsStatus: TypingsStatus,
|
||||||
|
private readonly fileConfigurationManager: FileConfigurationManager,
|
||||||
|
languageId: string
|
||||||
|
) {
|
||||||
|
|
||||||
|
this.setCompleteOption(languageId)
|
||||||
|
commands.register(new ApplyCompletionCodeActionCommand(this.client))
|
||||||
|
}
|
||||||
|
|
||||||
|
private setCompleteOption(languageId: string): void {
|
||||||
|
this.completeOption = this.fileConfigurationManager.getCompleteOptions(languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get completionItems
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {TextDocument} document
|
||||||
|
* @param {Position} position
|
||||||
|
* @param {CancellationToken} token
|
||||||
|
* @param {string} triggerCharacter
|
||||||
|
* @returns {Promise<CompletionItem[]>}
|
||||||
|
*/
|
||||||
|
public async provideCompletionItems(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken,
|
||||||
|
context: CompletionContext,
|
||||||
|
): Promise<CompletionItem[]> {
|
||||||
|
if (this.typingsStatus.isAcquiringTypings) {
|
||||||
|
workspace.showMessage('Acquiring typings...', 'warning')
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
let { uri } = document
|
||||||
|
const file = this.client.toPath(document.uri)
|
||||||
|
if (!file) return []
|
||||||
|
let preText = document.getText({
|
||||||
|
start: { line: position.line, character: 0 },
|
||||||
|
end: position
|
||||||
|
})
|
||||||
|
let { triggerCharacter } = context
|
||||||
|
|
||||||
|
if (!this.shouldTrigger(triggerCharacter, preText)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const { completeOption } = this
|
||||||
|
|
||||||
|
const args: Proto.CompletionsRequestArgs = {
|
||||||
|
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
||||||
|
includeExternalModuleExports: completeOption.autoImportSuggestions,
|
||||||
|
includeInsertTextCompletions: true,
|
||||||
|
triggerCharacter: triggerCharacter && triggerCharacter === '.' ? triggerCharacter : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
let msg: Proto.CompletionEntry[] | undefined
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('completions', args, token)
|
||||||
|
msg = response.body
|
||||||
|
if (!msg) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const completionItems: CompletionItem[] = []
|
||||||
|
for (const element of msg) {
|
||||||
|
let { kind } = element
|
||||||
|
if (kind === PConst.Kind.warning
|
||||||
|
|| kind === PConst.Kind.script) {
|
||||||
|
if (!completeOption.nameSuggestions || triggerCharacter == '.') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!completeOption.autoImportSuggestions && element.hasAction) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const item = convertCompletionEntry(
|
||||||
|
element,
|
||||||
|
uri,
|
||||||
|
position,
|
||||||
|
completeOption.useCodeSnippetsOnMethodSuggest,
|
||||||
|
)
|
||||||
|
completionItems.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return completionItems
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve complete item, could have documentation added
|
||||||
|
*
|
||||||
|
* @public
|
||||||
|
* @param {CompletionItem} item
|
||||||
|
* @param {CancellationToken} token
|
||||||
|
* @returns {Promise<CompletionItem>}
|
||||||
|
*/
|
||||||
|
public async resolveCompletionItem(
|
||||||
|
item: CompletionItem,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<CompletionItem> {
|
||||||
|
if (item == null) return undefined
|
||||||
|
|
||||||
|
let { uri, position, source } = item.data
|
||||||
|
const filepath = this.client.toPath(uri)
|
||||||
|
if (!filepath) return undefined
|
||||||
|
let document = workspace.getDocument(uri)
|
||||||
|
if (!document) return undefined
|
||||||
|
resolveItem(item, document)
|
||||||
|
const args: Proto.CompletionDetailsRequestArgs = {
|
||||||
|
...typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
filepath,
|
||||||
|
position
|
||||||
|
),
|
||||||
|
entryNames: [
|
||||||
|
source
|
||||||
|
? { name: item.label, source }
|
||||||
|
: item.label
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
let response: Proto.CompletionDetailsResponse
|
||||||
|
try {
|
||||||
|
response = await this.client.execute(
|
||||||
|
'completionEntryDetails',
|
||||||
|
args,
|
||||||
|
token
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
const details = response.body
|
||||||
|
if (!details || !details.length || !details[0]) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
const detail = details[0]
|
||||||
|
item.detail = detail.displayParts.length
|
||||||
|
? Previewer.plain(detail.displayParts)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
item.documentation = this.getDocumentation(detail)
|
||||||
|
const { command, additionalTextEdits } = this.getCodeActions(detail, filepath)
|
||||||
|
if (command) item.command = command
|
||||||
|
item.additionalTextEdits = additionalTextEdits
|
||||||
|
if (detail && item.insertTextFormat == InsertTextFormat.Snippet) {
|
||||||
|
this.createSnippetOfFunctionCall(item, detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCodeActions(
|
||||||
|
detail: Proto.CompletionEntryDetails,
|
||||||
|
filepath: string
|
||||||
|
): { command?: Command; additionalTextEdits?: TextEdit[] } {
|
||||||
|
if (!detail.codeActions || !detail.codeActions.length) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
let { commaAfterImport } = this.completeOption
|
||||||
|
// Try to extract out the additionalTextEdits for the current file.
|
||||||
|
// Also check if we still have to apply other workspace edits
|
||||||
|
const additionalTextEdits: TextEdit[] = []
|
||||||
|
let hasReaminingCommandsOrEdits = false
|
||||||
|
for (const tsAction of detail.codeActions) {
|
||||||
|
if (tsAction.commands) {
|
||||||
|
hasReaminingCommandsOrEdits = true
|
||||||
|
}
|
||||||
|
// Convert all edits in the current file using `additionalTextEdits`
|
||||||
|
if (tsAction.changes) {
|
||||||
|
for (const change of tsAction.changes) {
|
||||||
|
if (change.fileName === filepath) {
|
||||||
|
additionalTextEdits.push(
|
||||||
|
...change.textChanges.map(typeConverters.TextEdit.fromCodeEdit)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
hasReaminingCommandsOrEdits = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let command = null
|
||||||
|
|
||||||
|
if (hasReaminingCommandsOrEdits) {
|
||||||
|
// Create command that applies all edits not in the current file.
|
||||||
|
command = {
|
||||||
|
title: '',
|
||||||
|
command: ApplyCompletionCodeActionCommand.ID,
|
||||||
|
arguments: [
|
||||||
|
detail.codeActions.map((x): Proto.CodeAction => ({
|
||||||
|
commands: x.commands,
|
||||||
|
description: x.description,
|
||||||
|
changes: x.changes.filter(x => x.fileName !== filepath)
|
||||||
|
}))
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (additionalTextEdits.length && !commaAfterImport) {
|
||||||
|
// remove comma
|
||||||
|
additionalTextEdits.forEach(o => {
|
||||||
|
o.newText = o.newText.replace(/;/, '')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
command,
|
||||||
|
additionalTextEdits: additionalTextEdits.length
|
||||||
|
? additionalTextEdits
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldTrigger(
|
||||||
|
triggerCharacter: string,
|
||||||
|
pre: string,
|
||||||
|
): boolean {
|
||||||
|
if (triggerCharacter === '.') {
|
||||||
|
if (pre.match(/[\s\.'"]\.$/)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (triggerCharacter === '@') {
|
||||||
|
// make sure we are in something that looks like the start of a jsdoc comment
|
||||||
|
if (!pre.match(/^\s*\*[ ]?@/) && !pre.match(/\/\*\*+[ ]?@/)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (triggerCharacter === '<') {
|
||||||
|
return this.client.apiVersion.gte(API.v290)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// complete item documentation
|
||||||
|
private getDocumentation(detail: Proto.CompletionEntryDetails): MarkupContent | undefined {
|
||||||
|
let documentation = ''
|
||||||
|
if (detail.source) {
|
||||||
|
const importPath = `'${Previewer.plain(detail.source)}'`
|
||||||
|
const autoImportLabel = `Auto import from ${importPath}`
|
||||||
|
documentation += `${autoImportLabel}\n`
|
||||||
|
}
|
||||||
|
let parts = [
|
||||||
|
Previewer.plain(detail.documentation),
|
||||||
|
Previewer.tagsMarkdownPreview(detail.tags)
|
||||||
|
]
|
||||||
|
parts = parts.filter(s => s && s.trim() != '')
|
||||||
|
documentation += parts.join('\n\n')
|
||||||
|
if (documentation.length) {
|
||||||
|
return {
|
||||||
|
kind: MarkupKind.Markdown,
|
||||||
|
value: documentation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
private createSnippetOfFunctionCall(
|
||||||
|
item: CompletionItem,
|
||||||
|
detail: Proto.CompletionEntryDetails
|
||||||
|
): void {
|
||||||
|
let hasOptionalParameters = false
|
||||||
|
let hasAddedParameters = false
|
||||||
|
|
||||||
|
let snippet = ''
|
||||||
|
const methodName = detail.displayParts.find(
|
||||||
|
part => part.kind === 'methodName'
|
||||||
|
)
|
||||||
|
let { textEdit, data } = item
|
||||||
|
let { position, uri } = data
|
||||||
|
|
||||||
|
if (textEdit) {
|
||||||
|
snippet += item.insertText || textEdit.newText // tslint:disable-line
|
||||||
|
} else {
|
||||||
|
let document = workspace.getDocument(uri)
|
||||||
|
if (!document) return
|
||||||
|
let range = document.getWordRangeAtPosition(position)
|
||||||
|
textEdit = { range, newText: '' }
|
||||||
|
snippet += item.insertText || (methodName && methodName.text) || item.label // tslint:disable-line
|
||||||
|
}
|
||||||
|
snippet += '('
|
||||||
|
let holderIndex = 1
|
||||||
|
let parenCount = 0
|
||||||
|
let i = 0
|
||||||
|
for (; i < detail.displayParts.length; ++i) {
|
||||||
|
const part = detail.displayParts[i]
|
||||||
|
// Only take top level paren names
|
||||||
|
if (part.kind === 'parameterName' && parenCount === 1) {
|
||||||
|
const next = detail.displayParts[i + 1]
|
||||||
|
// Skip optional parameters
|
||||||
|
const nameIsFollowedByOptionalIndicator = next && next.text === '?'
|
||||||
|
if (!nameIsFollowedByOptionalIndicator) {
|
||||||
|
if (hasAddedParameters) snippet += ', '
|
||||||
|
hasAddedParameters = true
|
||||||
|
snippet += '${' + holderIndex + ':' + part.text + '}'
|
||||||
|
holderIndex = holderIndex + 1
|
||||||
|
}
|
||||||
|
hasOptionalParameters =
|
||||||
|
hasOptionalParameters || nameIsFollowedByOptionalIndicator
|
||||||
|
} else if (part.kind === 'punctuation') {
|
||||||
|
if (part.text === '(') {
|
||||||
|
++parenCount
|
||||||
|
} else if (part.text === ')') {
|
||||||
|
--parenCount
|
||||||
|
} else if (part.text === '...' && parenCount === 1) {
|
||||||
|
// Found rest parmeter. Do not fill in any further arguments
|
||||||
|
hasOptionalParameters = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasOptionalParameters) {
|
||||||
|
snippet += '${' + holderIndex + '}'
|
||||||
|
}
|
||||||
|
snippet += ')'
|
||||||
|
snippet += '$0'
|
||||||
|
textEdit.newText = snippet
|
||||||
|
item.textEdit = textEdit
|
||||||
|
}
|
||||||
|
}
|
64
src/server/features/definitionProvider.ts
Normal file
64
src/server/features/definitionProvider.ts
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Definition, Location, Position, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { DefinitionProvider, ImplementationProvider, TypeDefinitionProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptDefinitionProvider implements DefinitionProvider, TypeDefinitionProvider, ImplementationProvider {
|
||||||
|
constructor(private client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
protected async getSymbolLocations(
|
||||||
|
definitionType: 'definition' | 'implementation' | 'typeDefinition',
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken | boolean
|
||||||
|
): Promise<Location[] | undefined> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
filepath,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute(definitionType, args, token)
|
||||||
|
const locations: Proto.FileSpan[] = (response && response.body) || []
|
||||||
|
return locations.map(location =>
|
||||||
|
typeConverters.Location.fromTextSpan(
|
||||||
|
this.client.toResource(location.file),
|
||||||
|
location
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public provideDefinition(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken | boolean
|
||||||
|
): Promise<Definition | undefined> {
|
||||||
|
return this.getSymbolLocations('definition', document, position, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
public provideTypeDefinition(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken): Promise<Definition> {
|
||||||
|
return this.getSymbolLocations('typeDefinition', document, position, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
public provideImplementation(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken): Promise<Definition> {
|
||||||
|
return this.getSymbolLocations('implementation', document, position, token)
|
||||||
|
}
|
||||||
|
}
|
161
src/server/features/diagnostics.ts
Normal file
161
src/server/features/diagnostics.ts
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { Diagnostic } from 'vscode-languageserver-protocol'
|
||||||
|
import { languages, DiagnosticCollection } from 'coc.nvim'
|
||||||
|
import { ResourceMap } from './resourceMap'
|
||||||
|
|
||||||
|
export class DiagnosticSet {
|
||||||
|
private _map = new ResourceMap<Diagnostic[]>()
|
||||||
|
|
||||||
|
public set(uri: string, diagnostics: Diagnostic[]): void {
|
||||||
|
this._map.set(uri, diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(uri: string): Diagnostic[] {
|
||||||
|
return this._map.get(uri) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
this._map = new ResourceMap<Diagnostic[]>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DiagnosticKind {
|
||||||
|
Syntax,
|
||||||
|
Semantic,
|
||||||
|
Suggestion
|
||||||
|
}
|
||||||
|
|
||||||
|
const allDiagnosticKinds = [
|
||||||
|
DiagnosticKind.Syntax,
|
||||||
|
DiagnosticKind.Semantic,
|
||||||
|
DiagnosticKind.Suggestion
|
||||||
|
]
|
||||||
|
|
||||||
|
export class DiagnosticsManager {
|
||||||
|
private readonly _diagnostics = new Map<DiagnosticKind, DiagnosticSet>()
|
||||||
|
private readonly _currentDiagnostics: DiagnosticCollection
|
||||||
|
private _pendingUpdates = new ResourceMap<any>()
|
||||||
|
private _validate = true
|
||||||
|
private _enableSuggestions = true
|
||||||
|
|
||||||
|
private readonly updateDelay = 200
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
for (const kind of allDiagnosticKinds) {
|
||||||
|
this._diagnostics.set(kind, new DiagnosticSet())
|
||||||
|
}
|
||||||
|
this._currentDiagnostics = languages.createDiagnosticCollection('tsserver')
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this._currentDiagnostics.dispose()
|
||||||
|
for (const value of this._pendingUpdates.values) {
|
||||||
|
clearTimeout(value)
|
||||||
|
}
|
||||||
|
this._pendingUpdates = new ResourceMap<any>()
|
||||||
|
}
|
||||||
|
|
||||||
|
public reInitialize(): void {
|
||||||
|
this._currentDiagnostics.clear()
|
||||||
|
for (const diagnosticSet of this._diagnostics.values()) {
|
||||||
|
diagnosticSet.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public set validate(value: boolean) {
|
||||||
|
if (this._validate === value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this._validate = value
|
||||||
|
if (!value) {
|
||||||
|
this._currentDiagnostics.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public set enableSuggestions(value: boolean) {
|
||||||
|
if (this._enableSuggestions === value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this._enableSuggestions = value
|
||||||
|
if (!value) {
|
||||||
|
this._currentDiagnostics.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public diagnosticsReceived(
|
||||||
|
kind: DiagnosticKind,
|
||||||
|
uri: string,
|
||||||
|
diagnostics: Diagnostic[]
|
||||||
|
): void {
|
||||||
|
const collection = this._diagnostics.get(kind)
|
||||||
|
if (!collection) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diagnostics.length === 0) {
|
||||||
|
const existing = collection.get(uri)
|
||||||
|
if (existing.length === 0) {
|
||||||
|
// No need to update
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.set(uri, diagnostics)
|
||||||
|
|
||||||
|
this.scheduleDiagnosticsUpdate(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(uri: string): void {
|
||||||
|
this._currentDiagnostics.delete(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDiagnostics(uri: string): Diagnostic[] {
|
||||||
|
return this._currentDiagnostics.get(uri) || []
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
private scheduleDiagnosticsUpdate(uri: string): void {
|
||||||
|
if (!this._pendingUpdates.has(uri)) {
|
||||||
|
this._pendingUpdates.set(
|
||||||
|
uri,
|
||||||
|
setTimeout(() => this.updateCurrentDiagnostics(uri), this.updateDelay)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCurrentDiagnostics(uri: string): void {
|
||||||
|
if (this._pendingUpdates.has(uri)) {
|
||||||
|
clearTimeout(this._pendingUpdates.get(uri))
|
||||||
|
this._pendingUpdates.delete(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._validate) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const allDiagnostics = [
|
||||||
|
...this._diagnostics.get(DiagnosticKind.Syntax)!.get(uri),
|
||||||
|
...this._diagnostics.get(DiagnosticKind.Semantic)!.get(uri),
|
||||||
|
...this.getSuggestionDiagnostics(uri)
|
||||||
|
]
|
||||||
|
this._currentDiagnostics.set(uri, allDiagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSuggestionDiagnostics(uri: string): Diagnostic[] {
|
||||||
|
return this._diagnostics
|
||||||
|
.get(DiagnosticKind.Suggestion)!
|
||||||
|
.get(uri)
|
||||||
|
.filter(x => {
|
||||||
|
if (!this._enableSuggestions) {
|
||||||
|
// Still show unused
|
||||||
|
return x.code && x.code == 6133
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
76
src/server/features/directiveCommentCompletions.ts
Normal file
76
src/server/features/directiveCommentCompletions.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, CompletionList, Position, Range, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
|
||||||
|
interface Directive {
|
||||||
|
readonly value: string
|
||||||
|
readonly description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const directives: Directive[] = [
|
||||||
|
{
|
||||||
|
value: '@ts-check',
|
||||||
|
description: 'Enables semantic checking in a JavaScript file. Must be at the top of a file.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '@ts-nocheck',
|
||||||
|
description: 'Disables semantic checking in a JavaScript file. Must be at the top of a file.'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: '@ts-ignore',
|
||||||
|
description: 'Suppresses @ts-check errors on the next line of a file.'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default class DirectiveCommentCompletionProvider {
|
||||||
|
constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public provideCompletionItems(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
_token: CancellationToken,
|
||||||
|
context: CompletionContext
|
||||||
|
): CompletionItem[] | CompletionList {
|
||||||
|
if (context.triggerCharacter != '@') {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const file = this.client.toPath(document.uri)
|
||||||
|
if (!file) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const doc = workspace.getDocument(document.uri)
|
||||||
|
|
||||||
|
const line = doc.getline(position.line)
|
||||||
|
const prefix = line.slice(0, position.character)
|
||||||
|
const match = prefix.match(/^\s*\/\/+\s?(@[a-zA-Z\-]*)?$/)
|
||||||
|
if (match) {
|
||||||
|
let items = directives.map(directive => {
|
||||||
|
const item = CompletionItem.create(directive.value)
|
||||||
|
item.kind = CompletionItemKind.Snippet
|
||||||
|
item.detail = directive.description
|
||||||
|
item.textEdit = {
|
||||||
|
range: Range.create(
|
||||||
|
position.line,
|
||||||
|
Math.max(0, position.character - (match[1] ? match[1].length : 0)),
|
||||||
|
position.line,
|
||||||
|
position.character
|
||||||
|
),
|
||||||
|
newText: directive.value
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
let res: any = {
|
||||||
|
isIncomplete: false,
|
||||||
|
items
|
||||||
|
}
|
||||||
|
res.startcol = doc.fixStartcol(position, ['@'])
|
||||||
|
return res as any
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
48
src/server/features/documentHighlight.ts
Normal file
48
src/server/features/documentHighlight.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, DocumentHighlight, DocumentHighlightKind, Position, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { DocumentHighlightProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptDocumentHighlightProvider implements DocumentHighlightProvider {
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async provideDocumentHighlights(
|
||||||
|
resource: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<DocumentHighlight[]> {
|
||||||
|
const file = this.client.toPath(resource.uri)
|
||||||
|
if (!file) return []
|
||||||
|
|
||||||
|
const args = typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
file,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('occurrences', args, token)
|
||||||
|
if (response && response.body) {
|
||||||
|
return response.body
|
||||||
|
.filter(x => !x.isInString)
|
||||||
|
.map(documentHighlightFromOccurance)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function documentHighlightFromOccurance(
|
||||||
|
occurrence: Proto.OccurrencesResponseItem // tslint:disable-line
|
||||||
|
): DocumentHighlight {
|
||||||
|
return {
|
||||||
|
range: typeConverters.Range.fromTextSpan(occurrence),
|
||||||
|
kind: occurrence.isWriteAccess ? DocumentHighlightKind.Write : DocumentHighlightKind.Read
|
||||||
|
}
|
||||||
|
}
|
143
src/server/features/documentSymbol.ts
Normal file
143
src/server/features/documentSymbol.ts
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, DocumentSymbol, Range, SymbolKind, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { DocumentSymbolProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import * as PConst from '../protocol.const'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
const getSymbolKind = (kind: string): SymbolKind => {
|
||||||
|
switch (kind) {
|
||||||
|
case PConst.Kind.module:
|
||||||
|
return SymbolKind.Module
|
||||||
|
case PConst.Kind.class:
|
||||||
|
return SymbolKind.Class
|
||||||
|
case PConst.Kind.enum:
|
||||||
|
return SymbolKind.Enum
|
||||||
|
case PConst.Kind.interface:
|
||||||
|
return SymbolKind.Interface
|
||||||
|
case PConst.Kind.memberFunction:
|
||||||
|
return SymbolKind.Method
|
||||||
|
case PConst.Kind.memberVariable:
|
||||||
|
return SymbolKind.Property
|
||||||
|
case PConst.Kind.memberGetAccessor:
|
||||||
|
return SymbolKind.Property
|
||||||
|
case PConst.Kind.memberSetAccessor:
|
||||||
|
return SymbolKind.Property
|
||||||
|
case PConst.Kind.variable:
|
||||||
|
return SymbolKind.Variable
|
||||||
|
case PConst.Kind.const:
|
||||||
|
return SymbolKind.Variable
|
||||||
|
case PConst.Kind.localVariable:
|
||||||
|
return SymbolKind.Variable
|
||||||
|
case PConst.Kind.variable:
|
||||||
|
return SymbolKind.Variable
|
||||||
|
case PConst.Kind.constructSignature:
|
||||||
|
case PConst.Kind.constructorImplementation:
|
||||||
|
case PConst.Kind.function:
|
||||||
|
case PConst.Kind.localFunction:
|
||||||
|
return SymbolKind.Function
|
||||||
|
}
|
||||||
|
return SymbolKind.Variable
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolProvider {
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async provideDocumentSymbols(
|
||||||
|
resource: TextDocument,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<DocumentSymbol[]> {
|
||||||
|
const filepath = this.client.toPath(resource.uri)
|
||||||
|
if (!filepath) return []
|
||||||
|
|
||||||
|
const args: Proto.FileRequestArgs = {
|
||||||
|
file: filepath
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('navtree', args, token)
|
||||||
|
if (response.body) {
|
||||||
|
// The root represents the file. Ignore this when showing in the UI
|
||||||
|
const tree = response.body
|
||||||
|
if (tree.childItems) {
|
||||||
|
const result = new Array<DocumentSymbol>()
|
||||||
|
tree.childItems.forEach(item =>
|
||||||
|
TypeScriptDocumentSymbolProvider.convertNavTree(
|
||||||
|
result,
|
||||||
|
item
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
} catch (e) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static convertNavTree(
|
||||||
|
bucket: DocumentSymbol[],
|
||||||
|
item: Proto.NavigationTree,
|
||||||
|
): boolean {
|
||||||
|
let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item)
|
||||||
|
const children = new Set(item.childItems || [])
|
||||||
|
for (const span of item.spans) {
|
||||||
|
const range = typeConverters.Range.fromTextSpan(span)
|
||||||
|
const symbolInfo = DocumentSymbol.create(
|
||||||
|
item.text,
|
||||||
|
'',
|
||||||
|
getSymbolKind(item.kind),
|
||||||
|
range,
|
||||||
|
range)
|
||||||
|
symbolInfo.children = children.size > 0 ? [] : null
|
||||||
|
|
||||||
|
for (const child of children) {
|
||||||
|
if (child.spans.some(span => !!containsRange(range, typeConverters.Range.fromTextSpan(span)))) {
|
||||||
|
const includedChild = TypeScriptDocumentSymbolProvider.convertNavTree(symbolInfo.children, child)
|
||||||
|
shouldInclude = shouldInclude || includedChild
|
||||||
|
children.delete(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldInclude) {
|
||||||
|
bucket.push(symbolInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return shouldInclude
|
||||||
|
}
|
||||||
|
|
||||||
|
private static shouldInclueEntry(
|
||||||
|
item: Proto.NavigationTree | Proto.NavigationBarItem
|
||||||
|
): boolean {
|
||||||
|
if (item.kind === PConst.Kind.alias) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return !!(
|
||||||
|
item.text &&
|
||||||
|
item.text !== '<function>' &&
|
||||||
|
item.text !== '<class>'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function containsRange(range: Range, otherRange: Range): boolean {
|
||||||
|
if (otherRange.start.line < range.start.line || otherRange.end.line < range.start.line) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (otherRange.start.line > range.end.line || otherRange.end.line > range.end.line) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (otherRange.start.line === range.start.line && otherRange.start.character < range.start.character) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (otherRange.end.line === range.end.line && otherRange.end.character > range.end.character) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
175
src/server/features/fileConfigurationManager.ts
Normal file
175
src/server/features/fileConfigurationManager.ts
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { WorkspaceConfiguration, workspace } from 'coc.nvim'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import API from '../utils/api'
|
||||||
|
import * as languageIds from '../utils/languageModeIds'
|
||||||
|
|
||||||
|
function objAreEqual<T>(a: T, b: T): boolean {
|
||||||
|
let keys = Object.keys(a)
|
||||||
|
for (let i = 0; i < keys.length; i++) { // tslint:disable-line
|
||||||
|
let key = keys[i]
|
||||||
|
if ((a as any)[key] !== (b as any)[key]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FormatOptions {
|
||||||
|
tabSize: number
|
||||||
|
insertSpaces: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FileConfiguration {
|
||||||
|
formatOptions: Proto.FormatCodeSettings
|
||||||
|
preferences: Proto.UserPreferences
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CompletionOptions {
|
||||||
|
readonly commaAfterImport: boolean
|
||||||
|
readonly useCodeSnippetsOnMethodSuggest: boolean
|
||||||
|
readonly nameSuggestions: boolean
|
||||||
|
readonly autoImportSuggestions: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FileConfigurationManager {
|
||||||
|
private cachedOption = null
|
||||||
|
private requesting = false
|
||||||
|
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ensureConfigurationOptions(languageId: string, insertSpaces: boolean, tabSize: number): Promise<void> {
|
||||||
|
let { requesting } = this
|
||||||
|
let options: FormatOptions = {
|
||||||
|
tabSize,
|
||||||
|
insertSpaces
|
||||||
|
}
|
||||||
|
if (requesting || (this.cachedOption && objAreEqual(this.cachedOption, options))) return
|
||||||
|
const currentOptions = this.getFileOptions(options, languageId)
|
||||||
|
this.requesting = true
|
||||||
|
const args = {
|
||||||
|
hostInfo: 'nvim-coc',
|
||||||
|
...currentOptions
|
||||||
|
} as Proto.ConfigureRequestArguments
|
||||||
|
await this.client.execute('configure', args)
|
||||||
|
this.cachedOption = options
|
||||||
|
this.requesting = false
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ensureConfigurationForDocument(document: TextDocument): Promise<void> {
|
||||||
|
let opts = await workspace.getFormatOptions(document.uri)
|
||||||
|
return this.ensureConfigurationOptions(document.languageId, opts.insertSpaces, opts.tabSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
this.cachedOption = null
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLanguageConfiguration(languageId: string): WorkspaceConfiguration {
|
||||||
|
return workspace.getConfiguration(languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
public isTypeScriptDocument(languageId: string): boolean {
|
||||||
|
return languageId === languageIds.typescript || languageId === languageIds.typescriptreact ||
|
||||||
|
languageId === languageIds.typescripttsx || languageId === languageIds.typescriptjsx
|
||||||
|
}
|
||||||
|
|
||||||
|
public enableJavascript(): boolean {
|
||||||
|
const config = workspace.getConfiguration('tsserver')
|
||||||
|
return !!config.get<boolean>('enableJavascript')
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFileOptions(options: FormatOptions, languageId: string): FileConfiguration {
|
||||||
|
const lang = this.isTypeScriptDocument(languageId) ? 'typescript' : 'javascript'
|
||||||
|
return {
|
||||||
|
formatOptions: this.getFormatOptions(options, lang),
|
||||||
|
preferences: this.getPreferences(lang)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFormatOptions(options: FormatOptions, language: string): Proto.FormatCodeSettings {
|
||||||
|
const config = workspace.getConfiguration(`${language}.format`)
|
||||||
|
|
||||||
|
return {
|
||||||
|
tabSize: options.tabSize,
|
||||||
|
indentSize: options.tabSize,
|
||||||
|
convertTabsToSpaces: options.insertSpaces,
|
||||||
|
// We can use \n here since the editor normalizes later on to its line endings.
|
||||||
|
newLineCharacter: '\n',
|
||||||
|
insertSpaceAfterCommaDelimiter: config.get<boolean>('insertSpaceAfterCommaDelimiter'),
|
||||||
|
insertSpaceAfterConstructor: config.get<boolean>('insertSpaceAfterConstructor'),
|
||||||
|
insertSpaceAfterSemicolonInForStatements: config.get<boolean>('insertSpaceAfterSemicolonInForStatements'),
|
||||||
|
insertSpaceBeforeAndAfterBinaryOperators: config.get<boolean>('insertSpaceBeforeAndAfterBinaryOperators'),
|
||||||
|
insertSpaceAfterKeywordsInControlFlowStatements: config.get<boolean>('insertSpaceAfterKeywordsInControlFlowStatements'),
|
||||||
|
insertSpaceAfterFunctionKeywordForAnonymousFunctions: config.get<boolean>('insertSpaceAfterFunctionKeywordForAnonymousFunctions'),
|
||||||
|
insertSpaceBeforeFunctionParenthesis: config.get<boolean>('insertSpaceBeforeFunctionParenthesis'),
|
||||||
|
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis'),
|
||||||
|
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets'),
|
||||||
|
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces'),
|
||||||
|
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces'),
|
||||||
|
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: config.get<boolean>('insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces'),
|
||||||
|
insertSpaceAfterTypeAssertion: config.get<boolean>('insertSpaceAfterTypeAssertion'),
|
||||||
|
placeOpenBraceOnNewLineForFunctions: config.get<boolean>('placeOpenBraceOnNewLineForFunctions'),
|
||||||
|
placeOpenBraceOnNewLineForControlBlocks: config.get<boolean>('placeOpenBraceOnNewLineForControlBlocks')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCompleteOptions(languageId: string): CompletionOptions {
|
||||||
|
const lang = this.isTypeScriptDocument(languageId) ? 'typescript' : 'javascript'
|
||||||
|
const config = workspace.getConfiguration(`${lang}.preferences.completion`)
|
||||||
|
return {
|
||||||
|
useCodeSnippetsOnMethodSuggest: config.get<boolean>('useCodeSnippetsOnMethodSuggest', true),
|
||||||
|
commaAfterImport: config.get<boolean>('commaAfterImport', true),
|
||||||
|
nameSuggestions: config.get<boolean>('nameSuggestions', true),
|
||||||
|
autoImportSuggestions: config.get<boolean>('autoImportSuggestions', true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPreferences(language: string): Proto.UserPreferences {
|
||||||
|
if (!this.client.apiVersion.gte(API.v290)) {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
const config = workspace.getConfiguration(`${language}.preferences`)
|
||||||
|
return {
|
||||||
|
importModuleSpecifierPreference: getImportModuleSpecifier(config) as any,
|
||||||
|
disableSuggestions: !config.get<boolean>('suggestionActions.enabled', true),
|
||||||
|
quotePreference: getQuoteType(config),
|
||||||
|
includeCompletionsForModuleExports: config.get<boolean>('completion.moduleExports', true),
|
||||||
|
includeCompletionsWithInsertText: true,
|
||||||
|
allowTextChangesInNewFiles: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModuleImportType = 'relative' | 'non-relative' | 'auto'
|
||||||
|
type QuoteType = 'single' | 'double'
|
||||||
|
|
||||||
|
function getImportModuleSpecifier(config): ModuleImportType {
|
||||||
|
let val = config.get('importModuleSpecifier')
|
||||||
|
switch (val) {
|
||||||
|
case 'relative':
|
||||||
|
return 'relative'
|
||||||
|
case 'non-relative':
|
||||||
|
return 'non-relative'
|
||||||
|
default:
|
||||||
|
return 'auto'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQuoteType(config): QuoteType {
|
||||||
|
let val = config.get('quoteStyle')
|
||||||
|
switch (val) {
|
||||||
|
case 'single':
|
||||||
|
return 'single'
|
||||||
|
case 'double':
|
||||||
|
return 'double'
|
||||||
|
default:
|
||||||
|
return 'single'
|
||||||
|
}
|
||||||
|
}
|
70
src/server/features/folding.ts
Normal file
70
src/server/features/folding.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { CancellationToken } from 'vscode-jsonrpc'
|
||||||
|
import { FoldingRange, TextDocument } from 'vscode-languageserver-types'
|
||||||
|
import { FoldingContext, FoldingRangeProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptFoldingProvider implements FoldingRangeProvider {
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async provideFoldingRanges(
|
||||||
|
document: TextDocument,
|
||||||
|
_context: FoldingContext,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<FoldingRange[] | undefined> {
|
||||||
|
const file = this.client.toPath(document.uri)
|
||||||
|
if (!file) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const args: Proto.FileRequestArgs = { file }
|
||||||
|
const { body } = await this.client.execute('getOutliningSpans', args, token)
|
||||||
|
if (!body) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return body
|
||||||
|
.map(span => this.convertOutliningSpan(span, document))
|
||||||
|
.filter(foldingRange => !!foldingRange) as FoldingRange[]
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertOutliningSpan(
|
||||||
|
span: Proto.OutliningSpan,
|
||||||
|
document: TextDocument
|
||||||
|
): FoldingRange | undefined {
|
||||||
|
const range = typeConverters.Range.fromTextSpan(span.textSpan)
|
||||||
|
const kind = TypeScriptFoldingProvider.getFoldingRangeKind(span)
|
||||||
|
|
||||||
|
// Workaround for #49904
|
||||||
|
if (span.kind === 'comment') {
|
||||||
|
let doc = workspace.getDocument(document.uri)
|
||||||
|
const line = doc.getline(range.start.line)
|
||||||
|
if (line.match(/\/\/\s*#endregion/gi)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let { start, end } = range
|
||||||
|
return FoldingRange.create(start.line, end.line, start.character, end.character, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getFoldingRangeKind(
|
||||||
|
span: Proto.OutliningSpan
|
||||||
|
): string {
|
||||||
|
switch (span.kind) {
|
||||||
|
case 'comment':
|
||||||
|
case 'region':
|
||||||
|
case 'imports':
|
||||||
|
case 'code':
|
||||||
|
return span.kind
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
158
src/server/features/formatting.ts
Normal file
158
src/server/features/formatting.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, FormattingOptions, Position, Range, TextDocument, TextEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import { commands, workspace } from 'coc.nvim'
|
||||||
|
import { DocumentFormattingEditProvider, DocumentRangeFormattingEditProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import { languageIds } from '../utils/languageModeIds'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
import FileConfigurationManager from './fileConfigurationManager'
|
||||||
|
|
||||||
|
export default class TypeScriptFormattingProvider
|
||||||
|
implements
|
||||||
|
DocumentRangeFormattingEditProvider,
|
||||||
|
DocumentFormattingEditProvider {
|
||||||
|
public constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
private readonly formattingOptionsManager: FileConfigurationManager
|
||||||
|
) {
|
||||||
|
commands.register({
|
||||||
|
id: 'tsserver.format',
|
||||||
|
execute: async (): Promise<void> => {
|
||||||
|
let document = await workspace.document
|
||||||
|
if (!document) return
|
||||||
|
if (languageIds.indexOf(document.filetype) == -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let options = await workspace.getFormatOptions()
|
||||||
|
let edit = await this.provideDocumentFormattingEdits(
|
||||||
|
document.textDocument,
|
||||||
|
options
|
||||||
|
)
|
||||||
|
if (!edit) return
|
||||||
|
await document.applyEdits(workspace.nvim, edit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doFormat(
|
||||||
|
document: TextDocument,
|
||||||
|
options: FormattingOptions,
|
||||||
|
args: Proto.FormatRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<TextEdit[]> {
|
||||||
|
await this.formattingOptionsManager.ensureConfigurationOptions(
|
||||||
|
document.languageId,
|
||||||
|
options.insertSpaces,
|
||||||
|
options.tabSize
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('format', args, token)
|
||||||
|
if (response.body) {
|
||||||
|
return response.body.map(typeConverters.TextEdit.fromCodeEdit)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provideDocumentRangeFormattingEdits(
|
||||||
|
document: TextDocument,
|
||||||
|
range: Range,
|
||||||
|
options: FormattingOptions,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<TextEdit[]> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) return []
|
||||||
|
const args: Proto.FormatRequestArgs = {
|
||||||
|
file: filepath,
|
||||||
|
line: range.start.line + 1,
|
||||||
|
offset: range.start.character + 1,
|
||||||
|
endLine: range.end.line + 1,
|
||||||
|
endOffset: range.end.character + 1
|
||||||
|
}
|
||||||
|
return this.doFormat(document, options, args, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provideDocumentFormattingEdits(
|
||||||
|
document: TextDocument,
|
||||||
|
options: FormattingOptions,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<TextEdit[]> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) return []
|
||||||
|
const args: Proto.FormatRequestArgs = {
|
||||||
|
file: filepath,
|
||||||
|
line: 1,
|
||||||
|
offset: 1,
|
||||||
|
endLine: document.lineCount + 1,
|
||||||
|
endOffset: 1
|
||||||
|
}
|
||||||
|
return this.doFormat(document, options, args, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provideOnTypeFormattingEdits(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
ch: string,
|
||||||
|
options: FormattingOptions,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<TextEdit[]> {
|
||||||
|
if (!this.client.configuration.formatOnType) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const file = this.client.toPath(document.uri)
|
||||||
|
if (!file) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.formattingOptionsManager.ensureConfigurationOptions(
|
||||||
|
document.languageId,
|
||||||
|
options.insertSpaces,
|
||||||
|
options.tabSize
|
||||||
|
)
|
||||||
|
const doc = workspace.getDocument(document.uri)
|
||||||
|
|
||||||
|
const args: Proto.FormatOnKeyRequestArgs = {
|
||||||
|
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
||||||
|
key: ch
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const { body } = await this.client.execute('formatonkey', args, token)
|
||||||
|
const edits = body
|
||||||
|
const result: TextEdit[] = []
|
||||||
|
if (!edits) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
for (const edit of edits) {
|
||||||
|
const textEdit = typeConverters.TextEdit.fromCodeEdit(edit)
|
||||||
|
const range = textEdit.range
|
||||||
|
// Work around for https://github.com/Microsoft/TypeScript/issues/6700.
|
||||||
|
// Check if we have an edit at the beginning of the line which only removes white spaces and leaves
|
||||||
|
// an empty line. Drop those edits
|
||||||
|
if (
|
||||||
|
range.start.character === 0 &&
|
||||||
|
range.start.line === range.end.line &&
|
||||||
|
textEdit.newText === ''
|
||||||
|
) {
|
||||||
|
const lText = doc.getline(range.start.line)
|
||||||
|
// If the edit leaves something on the line keep the edit (note that the end character is exclusive).
|
||||||
|
// Keep it also if it removes something else than whitespace
|
||||||
|
if (lText.trim().length > 0 || lText.length > range.end.character) {
|
||||||
|
result.push(textEdit)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.push(textEdit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
54
src/server/features/hover.ts
Normal file
54
src/server/features/hover.ts
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Hover, MarkedString, Position, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { HoverProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import { tagsMarkdownPreview } from '../utils/previewer'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptHoverProvider implements HoverProvider {
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async provideHover(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<Hover | undefined> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const args = typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
filepath,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('quickinfo', args, token)
|
||||||
|
if (response && response.body) {
|
||||||
|
const data = response.body
|
||||||
|
return {
|
||||||
|
contents: TypeScriptHoverProvider.getContents(data),
|
||||||
|
range: typeConverters.Range.fromTextSpan(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getContents(data: Proto.QuickInfoResponseBody): MarkedString[] { // tslint:disable-line
|
||||||
|
const parts = []
|
||||||
|
|
||||||
|
if (data.displayString) {
|
||||||
|
parts.push({ language: 'typescript', value: data.displayString })
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = tagsMarkdownPreview(data.tags)
|
||||||
|
parts.push(data.documentation + (tags ? '\n\n' + tags : ''))
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
}
|
100
src/server/features/implementationsCodeLens.ts
Normal file
100
src/server/features/implementationsCodeLens.ts
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, CodeLens, Command, Location, Range, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import * as PConst from '../protocol.const'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider'
|
||||||
|
|
||||||
|
export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider {
|
||||||
|
public async resolveCodeLens(
|
||||||
|
codeLens: CodeLens,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<CodeLens> {
|
||||||
|
let { uri } = codeLens.data
|
||||||
|
let filepath = this.client.toPath(uri)
|
||||||
|
|
||||||
|
const args = typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
filepath,
|
||||||
|
codeLens.range.start
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('implementation', args, token)
|
||||||
|
if (response && response.body) {
|
||||||
|
const locations = response.body
|
||||||
|
.map(reference => {
|
||||||
|
return {
|
||||||
|
uri: this.client.toResource(reference.file),
|
||||||
|
range: {
|
||||||
|
start: typeConverters.Position.fromLocation(reference.start),
|
||||||
|
end: {
|
||||||
|
line: reference.start.line,
|
||||||
|
character: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Exclude original from implementations
|
||||||
|
.filter(
|
||||||
|
location => !(
|
||||||
|
location.uri.toString() === uri &&
|
||||||
|
location.range.start.line === codeLens.range.start.line &&
|
||||||
|
location.range.start.character ===
|
||||||
|
codeLens.range.start.character
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
codeLens.command = this.getCommand(locations, codeLens)
|
||||||
|
return codeLens
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
|
||||||
|
codeLens.command = {
|
||||||
|
title: 'Could not determine implementations',
|
||||||
|
command: ''
|
||||||
|
}
|
||||||
|
return codeLens
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCommand(
|
||||||
|
locations: Location[],
|
||||||
|
codeLens: CodeLens,
|
||||||
|
): Command | undefined {
|
||||||
|
let { uri } = codeLens.data
|
||||||
|
return {
|
||||||
|
title: this.getTitle(locations),
|
||||||
|
command: locations.length ? 'editor.action.showReferences' : '',
|
||||||
|
arguments: [uri, codeLens.range.start, locations]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTitle(locations: Location[]): string {
|
||||||
|
return locations.length === 1 ? '1 implementation' : `${locations.length} implementations`
|
||||||
|
}
|
||||||
|
|
||||||
|
protected extractSymbol(
|
||||||
|
document: TextDocument,
|
||||||
|
item: Proto.NavigationTree,
|
||||||
|
_parent: Proto.NavigationTree | null
|
||||||
|
): Range | null {
|
||||||
|
switch (item.kind) {
|
||||||
|
case PConst.Kind.interface:
|
||||||
|
return super.getSymbolRange(document, item)
|
||||||
|
|
||||||
|
case PConst.Kind.class:
|
||||||
|
case PConst.Kind.memberFunction:
|
||||||
|
case PConst.Kind.memberVariable:
|
||||||
|
case PConst.Kind.memberGetAccessor:
|
||||||
|
case PConst.Kind.memberSetAccessor:
|
||||||
|
if (item.kindModifiers.match(/\babstract\b/g)) {
|
||||||
|
return super.getSymbolRange(document, item)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
101
src/server/features/organizeImports.ts
Normal file
101
src/server/features/organizeImports.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { disposeAll, TextDocumentWillSaveEvent, workspace } from 'coc.nvim'
|
||||||
|
import { Command, CommandManager } from 'coc.nvim/lib/commands'
|
||||||
|
import { Disposable, TextDocument, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import { standardLanguageDescriptions } from '../utils/languageDescription'
|
||||||
|
import * as typeconverts from '../utils/typeConverters'
|
||||||
|
import FileConfigurationManager from './fileConfigurationManager'
|
||||||
|
|
||||||
|
class OrganizeImportsCommand implements Command {
|
||||||
|
public readonly id: string = 'tsserver.organizeImports'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
private commaAfterImport: boolean,
|
||||||
|
private modeIds: string[]
|
||||||
|
) {
|
||||||
|
workspace.onWillSaveUntil(this.onWillSaveUntil, this, 'tsserver-organizeImports')
|
||||||
|
}
|
||||||
|
|
||||||
|
private onWillSaveUntil(event: TextDocumentWillSaveEvent): void {
|
||||||
|
let config = workspace.getConfiguration('tsserver')
|
||||||
|
let format = config.get('orgnizeImportOnSave', false)
|
||||||
|
if (!format) return
|
||||||
|
let { document } = event
|
||||||
|
if (this.modeIds.indexOf(document.languageId) == -1) return
|
||||||
|
let willSaveWaitUntil = async (): Promise<TextEdit[]> => {
|
||||||
|
let edit = await this.getTextEdits(document)
|
||||||
|
if (!edit) return []
|
||||||
|
return edit.changes ? edit.changes[document.uri] : []
|
||||||
|
}
|
||||||
|
event.waitUntil(willSaveWaitUntil())
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getTextEdits(document: TextDocument): Promise<WorkspaceEdit | null> {
|
||||||
|
let file = this.client.toPath(document.uri)
|
||||||
|
const args: Proto.OrganizeImportsRequestArgs = {
|
||||||
|
scope: {
|
||||||
|
type: 'file',
|
||||||
|
args: {
|
||||||
|
file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const response = await this.client.execute('organizeImports', args)
|
||||||
|
if (!response || !response.success) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const edit = typeconverts.WorkspaceEdit.fromFileCodeEdits(
|
||||||
|
this.client,
|
||||||
|
response.body
|
||||||
|
)
|
||||||
|
if (!this.commaAfterImport) {
|
||||||
|
let { changes } = edit
|
||||||
|
if (changes) {
|
||||||
|
for (let c of Object.keys(changes)) {
|
||||||
|
for (let textEdit of changes[c]) {
|
||||||
|
textEdit.newText = textEdit.newText.replace(/;/g, '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return edit
|
||||||
|
}
|
||||||
|
|
||||||
|
public async execute(): Promise<void> {
|
||||||
|
let document = await workspace.document
|
||||||
|
if (this.modeIds.indexOf(document.filetype) == -1) return
|
||||||
|
let edit = await this.getTextEdits(document.textDocument)
|
||||||
|
if (edit) await workspace.applyEdit(edit)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class OrganizeImports {
|
||||||
|
private disposables: Disposable[] = []
|
||||||
|
public constructor(
|
||||||
|
client: ITypeScriptServiceClient,
|
||||||
|
commandManager: CommandManager,
|
||||||
|
fileConfigurationManager: FileConfigurationManager,
|
||||||
|
languageId: string
|
||||||
|
) {
|
||||||
|
let description = standardLanguageDescriptions.find(o => o.id == languageId)
|
||||||
|
let modeIds = description ? description.modeIds : []
|
||||||
|
let option = fileConfigurationManager.getCompleteOptions(languageId)
|
||||||
|
let cmd = new OrganizeImportsCommand(client, option.commaAfterImport, modeIds)
|
||||||
|
commandManager.register(cmd)
|
||||||
|
this.disposables.push(Disposable.create(() => {
|
||||||
|
commandManager.unregister(cmd.id)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
}
|
||||||
|
}
|
49
src/server/features/projectError.ts
Normal file
49
src/server/features/projectError.ts
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import { disposeAll, workspace } from 'coc.nvim'
|
||||||
|
import { Command, CommandManager } from 'coc.nvim/lib/commands'
|
||||||
|
import { Disposable } from 'vscode-languageserver-protocol'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as languageIds from '../utils/languageModeIds'
|
||||||
|
|
||||||
|
class ProjectErrorCommand implements Command {
|
||||||
|
public readonly id: string = 'tsserver.project_error'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async execute(): Promise<void> {
|
||||||
|
let document = await workspace.document
|
||||||
|
if (languageIds[document.filetype] == null) return
|
||||||
|
let file = this.client.toPath(document.uri)
|
||||||
|
const args: Proto.GeterrForProjectRequestArgs = {
|
||||||
|
file,
|
||||||
|
delay: 20
|
||||||
|
}
|
||||||
|
const response = await this.client.execute('geterrForProject', args)
|
||||||
|
if (!response || !response.success) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ProjectErrors {
|
||||||
|
private disposables: Disposable[] = []
|
||||||
|
public constructor(
|
||||||
|
client: ITypeScriptServiceClient,
|
||||||
|
commandManager: CommandManager
|
||||||
|
) {
|
||||||
|
let cmd = new ProjectErrorCommand(client)
|
||||||
|
commandManager.register(cmd)
|
||||||
|
this.disposables.push(Disposable.create(() => {
|
||||||
|
commandManager.unregister(cmd.id)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
}
|
||||||
|
}
|
284
src/server/features/quickfix.ts
Normal file
284
src/server/features/quickfix.ts
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { commands, workspace } from 'coc.nvim'
|
||||||
|
import { Command } from 'coc.nvim/lib/commands'
|
||||||
|
import { CodeActionProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import { CancellationToken, CodeAction, CodeActionContext, CodeActionKind, Diagnostic, Range, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import API from '../utils/api'
|
||||||
|
import { applyCodeActionCommands, getEditForCodeAction } from '../utils/codeAction'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
import BufferSyncSupport from './bufferSyncSupport'
|
||||||
|
import { DiagnosticsManager } from './diagnostics'
|
||||||
|
|
||||||
|
class ApplyCodeActionCommand implements Command {
|
||||||
|
public static readonly ID = '_typescript.applyCodeActionCommand'
|
||||||
|
public readonly id = ApplyCodeActionCommand.ID
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async execute(action: Proto.CodeFixAction): Promise<boolean> {
|
||||||
|
return applyCodeActionCommands(this.client, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ApplyFixAllCodeAction implements Command {
|
||||||
|
public static readonly ID = '_typescript.applyFixAllCodeAction'
|
||||||
|
public readonly id = ApplyFixAllCodeAction.ID
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async execute(
|
||||||
|
file: string,
|
||||||
|
tsAction: Proto.CodeFixAction
|
||||||
|
): Promise<void> {
|
||||||
|
if (!tsAction.fixId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const args: Proto.GetCombinedCodeFixRequestArgs = {
|
||||||
|
scope: {
|
||||||
|
type: 'file',
|
||||||
|
args: { file }
|
||||||
|
},
|
||||||
|
fixId: tsAction.fixId
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { body } = await this.client.execute('getCombinedCodeFix', args)
|
||||||
|
if (!body) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(
|
||||||
|
this.client,
|
||||||
|
body.changes
|
||||||
|
)
|
||||||
|
await workspace.applyEdit(edit)
|
||||||
|
const token = CancellationToken.None
|
||||||
|
|
||||||
|
const { commands } = body
|
||||||
|
if (commands && commands.length) {
|
||||||
|
for (const command of commands) {
|
||||||
|
await this.client.execute('applyCodeActionCommand', { command }, token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unique set of diagnostics keyed on diagnostic range and error code.
|
||||||
|
*/
|
||||||
|
class DiagnosticsSet {
|
||||||
|
public static from(diagnostics: Diagnostic[]): DiagnosticsSet {
|
||||||
|
const values = new Map<string, Diagnostic>()
|
||||||
|
for (const diagnostic of diagnostics) {
|
||||||
|
values.set(DiagnosticsSet.key(diagnostic), diagnostic)
|
||||||
|
}
|
||||||
|
return new DiagnosticsSet(values)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static key(diagnostic: Diagnostic): string {
|
||||||
|
const { start, end } = diagnostic.range
|
||||||
|
return `${diagnostic.code}-${start.line},${start.character}-${end.line},${end.character}`
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
private readonly _values: Map<string, Diagnostic>
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public get values(): Iterable<Diagnostic> {
|
||||||
|
return this._values.values()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SupportedCodeActionProvider {
|
||||||
|
private _supportedCodeActions?: Thenable<Set<number>>
|
||||||
|
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async getFixableDiagnosticsForContext(
|
||||||
|
context: CodeActionContext
|
||||||
|
): Promise<Diagnostic[]> {
|
||||||
|
const supportedActions = await this.supportedCodeActions
|
||||||
|
const fixableDiagnostics = DiagnosticsSet.from(
|
||||||
|
context.diagnostics.filter(diagnostic =>
|
||||||
|
supportedActions.has(+diagnostic.code!)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return Array.from(fixableDiagnostics.values)
|
||||||
|
}
|
||||||
|
|
||||||
|
private get supportedCodeActions(): Promise<Set<number>> {
|
||||||
|
if (!this._supportedCodeActions) {
|
||||||
|
this._supportedCodeActions = this.client
|
||||||
|
.execute('getSupportedCodeFixes', null, undefined)
|
||||||
|
.then(response => response.body || [])
|
||||||
|
.then(codes => codes.map(code => +code).filter(code => !isNaN(code)))
|
||||||
|
.then(codes => new Set(codes))
|
||||||
|
}
|
||||||
|
return Promise.resolve(this._supportedCodeActions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TypeScriptQuickFixProvider implements CodeActionProvider {
|
||||||
|
private readonly supportedCodeActionProvider: SupportedCodeActionProvider
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
private readonly diagnosticsManager: DiagnosticsManager,
|
||||||
|
private readonly bufferSyncSupport: BufferSyncSupport,
|
||||||
|
) {
|
||||||
|
commands.register(
|
||||||
|
new ApplyCodeActionCommand(client)
|
||||||
|
)
|
||||||
|
commands.register(
|
||||||
|
new ApplyFixAllCodeAction(client)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.supportedCodeActionProvider = new SupportedCodeActionProvider(client)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provideCodeActions(
|
||||||
|
document: TextDocument,
|
||||||
|
_range: Range,
|
||||||
|
context: CodeActionContext,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<CodeAction[]> {
|
||||||
|
if (!this.client.apiVersion.gte(API.v213)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = this.client.toPath(document.uri)
|
||||||
|
if (!file) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixableDiagnostics = await this.supportedCodeActionProvider.getFixableDiagnosticsForContext(
|
||||||
|
context
|
||||||
|
)
|
||||||
|
if (!fixableDiagnostics.length) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.bufferSyncSupport.hasPendingDiagnostics(document.uri)) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: CodeAction[] = []
|
||||||
|
for (const diagnostic of fixableDiagnostics) {
|
||||||
|
results.push(
|
||||||
|
...(await this.getFixesForDiagnostic(document, file, diagnostic, token))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFixesForDiagnostic(
|
||||||
|
document: TextDocument,
|
||||||
|
file: string,
|
||||||
|
diagnostic: Diagnostic,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<Iterable<CodeAction>> {
|
||||||
|
const args: Proto.CodeFixRequestArgs = {
|
||||||
|
...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
|
||||||
|
errorCodes: [+diagnostic.code!]
|
||||||
|
}
|
||||||
|
const codeFixesResponse = await this.client.execute(
|
||||||
|
'getCodeFixes',
|
||||||
|
args,
|
||||||
|
token
|
||||||
|
)
|
||||||
|
if (codeFixesResponse.body) {
|
||||||
|
const results: CodeAction[] = []
|
||||||
|
for (const tsCodeFix of codeFixesResponse.body) {
|
||||||
|
results.push(
|
||||||
|
...(await this.getAllFixesForTsCodeAction(
|
||||||
|
document,
|
||||||
|
file,
|
||||||
|
diagnostic,
|
||||||
|
tsCodeFix
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getAllFixesForTsCodeAction(
|
||||||
|
document: TextDocument,
|
||||||
|
file: string,
|
||||||
|
diagnostic: Diagnostic,
|
||||||
|
tsAction: Proto.CodeAction
|
||||||
|
): Promise<Iterable<CodeAction>> {
|
||||||
|
const singleFix = this.getSingleFixForTsCodeAction(diagnostic, tsAction)
|
||||||
|
const fixAll = await this.getFixAllForTsCodeAction(
|
||||||
|
document,
|
||||||
|
file,
|
||||||
|
diagnostic,
|
||||||
|
tsAction as Proto.CodeFixAction
|
||||||
|
)
|
||||||
|
return fixAll ? [singleFix, fixAll] : [singleFix]
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSingleFixForTsCodeAction(
|
||||||
|
diagnostic: Diagnostic,
|
||||||
|
tsAction: Proto.CodeAction
|
||||||
|
): CodeAction {
|
||||||
|
const codeAction: CodeAction = {
|
||||||
|
title: tsAction.description,
|
||||||
|
kind: CodeActionKind.QuickFix
|
||||||
|
}
|
||||||
|
codeAction.edit = getEditForCodeAction(this.client, tsAction)
|
||||||
|
codeAction.diagnostics = [diagnostic]
|
||||||
|
if (tsAction.commands) {
|
||||||
|
codeAction.command = {
|
||||||
|
command: ApplyCodeActionCommand.ID,
|
||||||
|
arguments: [tsAction],
|
||||||
|
title: tsAction.description
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return codeAction
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFixAllForTsCodeAction(
|
||||||
|
document: TextDocument,
|
||||||
|
file: string,
|
||||||
|
diagnostic: Diagnostic,
|
||||||
|
tsAction: Proto.CodeFixAction
|
||||||
|
): Promise<CodeAction | undefined> {
|
||||||
|
if (!tsAction.fixId || !this.client.apiVersion.gte(API.v270)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there are multiple diagnostics of the same type in the file
|
||||||
|
if (!this.diagnosticsManager
|
||||||
|
.getDiagnostics(document.uri)
|
||||||
|
.some(x => x.code === diagnostic.code && x !== diagnostic)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const action: CodeAction = {
|
||||||
|
title: tsAction.fixAllDescription || 'Fix all in file',
|
||||||
|
kind: CodeActionKind.QuickFix
|
||||||
|
}
|
||||||
|
action.diagnostics = [diagnostic]
|
||||||
|
action.command = {
|
||||||
|
command: ApplyFixAllCodeAction.ID,
|
||||||
|
arguments: [file, tsAction],
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
return action
|
||||||
|
}
|
||||||
|
}
|
221
src/server/features/refactor.ts
Normal file
221
src/server/features/refactor.ts
Normal file
|
@ -0,0 +1,221 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, CodeAction, CodeActionContext, CodeActionKind, Range, TextDocument, WorkspaceEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import { Command } from 'coc.nvim/lib/commands'
|
||||||
|
import { CodeActionProvider, CodeActionProviderMetadata } from 'coc.nvim/lib/provider'
|
||||||
|
import { workspace, commands } from 'coc.nvim'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
import FormattingOptionsManager from './fileConfigurationManager'
|
||||||
|
|
||||||
|
class ApplyRefactoringCommand implements Command {
|
||||||
|
public static readonly ID = '_typescript.applyRefactoring'
|
||||||
|
public readonly id = ApplyRefactoringCommand.ID
|
||||||
|
|
||||||
|
constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async execute(
|
||||||
|
document: TextDocument,
|
||||||
|
file: string,
|
||||||
|
refactor: string,
|
||||||
|
action: string,
|
||||||
|
range: Range
|
||||||
|
): Promise<boolean> {
|
||||||
|
const args: Proto.GetEditsForRefactorRequestArgs = {
|
||||||
|
...typeConverters.Range.toFileRangeRequestArgs(file, range),
|
||||||
|
refactor,
|
||||||
|
action
|
||||||
|
}
|
||||||
|
const response = await this.client.execute('getEditsForRefactor', args)
|
||||||
|
const body = response && response.body
|
||||||
|
if (!body || !body.edits.length) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceEdit = await this.toWorkspaceEdit(body)
|
||||||
|
if (!(await workspace.applyEdit(workspaceEdit))) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const renameLocation = body.renameLocation
|
||||||
|
if (renameLocation) {
|
||||||
|
commands.executeCommand('editor.action.rename',
|
||||||
|
document.uri,
|
||||||
|
typeConverters.Position.fromLocation(renameLocation)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
this.client,
|
||||||
|
body.edits
|
||||||
|
)
|
||||||
|
return workspaceEdit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SelectRefactorCommand implements Command {
|
||||||
|
public static readonly ID = '_typescript.selectRefactoring'
|
||||||
|
public readonly id = SelectRefactorCommand.ID
|
||||||
|
|
||||||
|
constructor(private readonly doRefactoring: ApplyRefactoringCommand) { }
|
||||||
|
|
||||||
|
public async execute(
|
||||||
|
document: TextDocument,
|
||||||
|
file: string,
|
||||||
|
info: Proto.ApplicableRefactorInfo,
|
||||||
|
range: Range
|
||||||
|
): Promise<boolean> {
|
||||||
|
let { actions } = info
|
||||||
|
const idx = actions.length == 1 ? 0 : await workspace.showQuickpick(
|
||||||
|
actions.map(action => action.description || action.name)
|
||||||
|
)
|
||||||
|
if (idx == -1) return false
|
||||||
|
let label = info.actions[idx].name
|
||||||
|
if (!label) return false
|
||||||
|
return this.doRefactoring.execute(
|
||||||
|
document,
|
||||||
|
file,
|
||||||
|
info.name,
|
||||||
|
label,
|
||||||
|
range
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TypeScriptRefactorProvider implements CodeActionProvider {
|
||||||
|
private static readonly extractFunctionKind = CodeActionKind.RefactorExtract + '.function'
|
||||||
|
private static readonly extractConstantKind = CodeActionKind.RefactorExtract + '.constant'
|
||||||
|
private static readonly moveKind = CodeActionKind.Refactor + '.move'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
private readonly formattingOptionsManager: FormattingOptionsManager,
|
||||||
|
) {
|
||||||
|
const doRefactoringCommand = commands.register(
|
||||||
|
new ApplyRefactoringCommand(this.client)
|
||||||
|
)
|
||||||
|
commands.register(new SelectRefactorCommand(doRefactoringCommand))
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly metadata: CodeActionProviderMetadata = {
|
||||||
|
providedCodeActionKinds: [CodeActionKind.Refactor]
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provideCodeActions(
|
||||||
|
document: TextDocument,
|
||||||
|
range: Range,
|
||||||
|
context: CodeActionContext,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<CodeAction[] | undefined> {
|
||||||
|
if (!this.shouldTrigger(context)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const file = this.client.toPath(document.uri)
|
||||||
|
if (!file) return undefined
|
||||||
|
await this.formattingOptionsManager.ensureConfigurationForDocument(document)
|
||||||
|
const args: Proto.GetApplicableRefactorsRequestArgs = typeConverters.Range.toFileRangeRequestArgs(
|
||||||
|
file,
|
||||||
|
range
|
||||||
|
)
|
||||||
|
let response: Proto.GetApplicableRefactorsResponse
|
||||||
|
try {
|
||||||
|
response = await this.client.execute('getApplicableRefactors', args, token)
|
||||||
|
if (!response || !response.body) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.convertApplicableRefactors(
|
||||||
|
response.body,
|
||||||
|
document,
|
||||||
|
file,
|
||||||
|
range
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertApplicableRefactors(
|
||||||
|
body: Proto.ApplicableRefactorInfo[],
|
||||||
|
document: TextDocument,
|
||||||
|
file: string,
|
||||||
|
rangeOrSelection: Range
|
||||||
|
): CodeAction[] {
|
||||||
|
const actions: CodeAction[] = []
|
||||||
|
for (const info of body) {
|
||||||
|
if (!info.inlineable) {
|
||||||
|
const codeAction: CodeAction = {
|
||||||
|
title: info.description,
|
||||||
|
kind: CodeActionKind.Refactor
|
||||||
|
}
|
||||||
|
codeAction.command = {
|
||||||
|
title: info.description,
|
||||||
|
command: SelectRefactorCommand.ID,
|
||||||
|
arguments: [document, file, info, rangeOrSelection]
|
||||||
|
}
|
||||||
|
actions.push(codeAction)
|
||||||
|
} else {
|
||||||
|
for (const action of info.actions) {
|
||||||
|
actions.push(
|
||||||
|
this.refactorActionToCodeAction(
|
||||||
|
action,
|
||||||
|
document,
|
||||||
|
file,
|
||||||
|
info,
|
||||||
|
rangeOrSelection
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return actions
|
||||||
|
}
|
||||||
|
|
||||||
|
private refactorActionToCodeAction(
|
||||||
|
action: Proto.RefactorActionInfo,
|
||||||
|
document: TextDocument,
|
||||||
|
file: string,
|
||||||
|
info: Proto.ApplicableRefactorInfo,
|
||||||
|
rangeOrSelection: Range
|
||||||
|
): CodeAction {
|
||||||
|
const codeAction: CodeAction = {
|
||||||
|
title: action.description,
|
||||||
|
kind: TypeScriptRefactorProvider.getKind(action)
|
||||||
|
}
|
||||||
|
codeAction.command = {
|
||||||
|
title: action.description,
|
||||||
|
command: ApplyRefactoringCommand.ID,
|
||||||
|
arguments: [document, file, info.name, action.name, rangeOrSelection]
|
||||||
|
}
|
||||||
|
return codeAction
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldTrigger(context: CodeActionContext): boolean {
|
||||||
|
if (
|
||||||
|
context.only &&
|
||||||
|
context.only.indexOf(CodeActionKind.Refactor) == -1
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getKind(refactor: Proto.RefactorActionInfo): string {
|
||||||
|
if (refactor.name.startsWith('function_')) {
|
||||||
|
return TypeScriptRefactorProvider.extractFunctionKind
|
||||||
|
} else if (refactor.name.startsWith('constant_')) {
|
||||||
|
return TypeScriptRefactorProvider.extractConstantKind
|
||||||
|
} else if (refactor.name.startsWith('Move')) {
|
||||||
|
return TypeScriptRefactorProvider.moveKind
|
||||||
|
}
|
||||||
|
return CodeActionKind.Refactor
|
||||||
|
}
|
||||||
|
}
|
46
src/server/features/references.ts
Normal file
46
src/server/features/references.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Location, Position, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { ReferenceContext, ReferenceProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptReferences implements ReferenceProvider {
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async provideReferences(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
context: ReferenceContext,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<Location[]> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) return []
|
||||||
|
|
||||||
|
const args = typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
filepath,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
const msg = await this.client.execute('references', args, token)
|
||||||
|
if (!msg.body) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
const result: Location[] = []
|
||||||
|
for (const ref of msg.body.refs) {
|
||||||
|
if (!context.includeDeclaration && ref.isDefinition) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const url = this.client.toResource(ref.file)
|
||||||
|
const location = typeConverters.Location.fromTextSpan(url, ref)
|
||||||
|
result.push(location)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
} catch {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
102
src/server/features/referencesCodeLens.ts
Normal file
102
src/server/features/referencesCodeLens.ts
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, CodeLens, Range, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import * as PConst from '../protocol.const'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
import { TypeScriptBaseCodeLensProvider } from './baseCodeLensProvider'
|
||||||
|
|
||||||
|
export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider {
|
||||||
|
public resolveCodeLens(
|
||||||
|
codeLens: CodeLens,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<CodeLens> {
|
||||||
|
let { uri } = codeLens.data
|
||||||
|
let filepath = this.client.toPath(uri)
|
||||||
|
const args = typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
filepath,
|
||||||
|
codeLens.range.start
|
||||||
|
)
|
||||||
|
return this.client
|
||||||
|
.execute('references', args, token)
|
||||||
|
.then(response => {
|
||||||
|
if (!response || !response.body) {
|
||||||
|
throw codeLens
|
||||||
|
}
|
||||||
|
|
||||||
|
const locations = response.body.refs
|
||||||
|
.map(reference =>
|
||||||
|
typeConverters.Location.fromTextSpan(
|
||||||
|
this.client.toResource(reference.file),
|
||||||
|
reference
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter(
|
||||||
|
location =>
|
||||||
|
// Exclude original definition from references
|
||||||
|
!(
|
||||||
|
location.uri.toString() === uri &&
|
||||||
|
location.range.start.line === codeLens.range.start.line &&
|
||||||
|
location.range.start.character ===
|
||||||
|
codeLens.range.start.character
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
codeLens.command = {
|
||||||
|
title: locations.length === 1 ? '1 reference' : `${locations.length} references`,
|
||||||
|
command: locations.length ? 'editor.action.showReferences' : '',
|
||||||
|
arguments: [uri, codeLens.range.start, locations]
|
||||||
|
}
|
||||||
|
return codeLens
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
codeLens.command = {
|
||||||
|
title: 'Could not determine references',
|
||||||
|
command: ''
|
||||||
|
}
|
||||||
|
return codeLens
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected extractSymbol(
|
||||||
|
document: TextDocument,
|
||||||
|
item: Proto.NavigationTree,
|
||||||
|
parent: Proto.NavigationTree | null
|
||||||
|
): Range | null {
|
||||||
|
if (parent && parent.kind === PConst.Kind.enum) {
|
||||||
|
return super.getSymbolRange(document, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (item.kind) {
|
||||||
|
case PConst.Kind.const:
|
||||||
|
case PConst.Kind.let:
|
||||||
|
case PConst.Kind.variable:
|
||||||
|
case PConst.Kind.function:
|
||||||
|
// Only show references for exported variables
|
||||||
|
if (!item.kindModifiers.match(/\bexport\b/)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
|
||||||
|
case PConst.Kind.class:
|
||||||
|
if (item.text === '<class>') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// fallthrough
|
||||||
|
|
||||||
|
case PConst.Kind.memberFunction:
|
||||||
|
case PConst.Kind.memberVariable:
|
||||||
|
case PConst.Kind.memberGetAccessor:
|
||||||
|
case PConst.Kind.memberSetAccessor:
|
||||||
|
case PConst.Kind.constructorImplementation:
|
||||||
|
case PConst.Kind.interface:
|
||||||
|
case PConst.Kind.type:
|
||||||
|
case PConst.Kind.enum:
|
||||||
|
return super.getSymbolRange(document, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
70
src/server/features/rename.ts
Normal file
70
src/server/features/rename.ts
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Position, TextDocument, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import { RenameProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptRenameProvider implements RenameProvider {
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async provideRenameEdits(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
newName: string,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<WorkspaceEdit | null> {
|
||||||
|
const file = this.client.toPath(document.uri)
|
||||||
|
if (!file) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const args: Proto.RenameRequestArgs = {
|
||||||
|
...typeConverters.Position.toFileLocationRequestArgs(file, position),
|
||||||
|
findInStrings: false,
|
||||||
|
findInComments: false
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('rename', args, token)
|
||||||
|
if (!response.body) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const renameInfo = response.body.info
|
||||||
|
if (!renameInfo.canRename) {
|
||||||
|
return Promise.reject<WorkspaceEdit>(
|
||||||
|
renameInfo.localizedErrorMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.toWorkspaceEdit(response.body.locs, newName)
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private toWorkspaceEdit(
|
||||||
|
locations: ReadonlyArray<Proto.SpanGroup>,
|
||||||
|
newName: string
|
||||||
|
): WorkspaceEdit {
|
||||||
|
let changes: { [uri: string]: TextEdit[] } = {}
|
||||||
|
for (const spanGroup of locations) {
|
||||||
|
const uri = this.client.toResource(spanGroup.file)
|
||||||
|
if (uri) {
|
||||||
|
changes[uri] = []
|
||||||
|
for (const textSpan of spanGroup.locs) {
|
||||||
|
changes[uri].push({
|
||||||
|
range: typeConverters.Range.fromTextSpan(textSpan),
|
||||||
|
newText: newName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { changes }
|
||||||
|
}
|
||||||
|
}
|
81
src/server/features/resourceMap.ts
Normal file
81
src/server/features/resourceMap.ts
Normal file
|
@ -0,0 +1,81 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps of file resources
|
||||||
|
*
|
||||||
|
* Attempts to handle correct mapping on both case sensitive and case in-sensitive
|
||||||
|
* file systems.
|
||||||
|
*/
|
||||||
|
export class ResourceMap<T> {
|
||||||
|
private readonly _map = new Map<string, T>()
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly _normalizePath?: (resource: string) => string | null
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public has(resource: string): boolean {
|
||||||
|
const file = this.toKey(resource)
|
||||||
|
return !!file && this._map.has(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get(resource: string): T | undefined {
|
||||||
|
const file = this.toKey(resource)
|
||||||
|
return file ? this._map.get(file) : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
public set(resource: string, value: T): void {
|
||||||
|
const file = this.toKey(resource)
|
||||||
|
if (file) {
|
||||||
|
this._map.set(file, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(resource: string): void {
|
||||||
|
const file = this.toKey(resource)
|
||||||
|
if (file) {
|
||||||
|
this._map.delete(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get values(): Iterable<T> {
|
||||||
|
return this._map.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
public get keys(): Iterable<string> {
|
||||||
|
return this._map.keys()
|
||||||
|
}
|
||||||
|
|
||||||
|
private toKey(resource: string): string | null {
|
||||||
|
const key = this._normalizePath
|
||||||
|
? this._normalizePath(resource)
|
||||||
|
: resource
|
||||||
|
if (!key) {
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
return this.isCaseInsensitivePath(key) ? key.toLowerCase() : key
|
||||||
|
}
|
||||||
|
|
||||||
|
private isCaseInsensitivePath(path: string): boolean {
|
||||||
|
if (isWindowsPath(path)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return path[0] === '/' && this.onIsCaseInsenitiveFileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
private get onIsCaseInsenitiveFileSystem(): boolean {
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isWindowsPath(path: string): boolean {
|
||||||
|
return /^[a-zA-Z]:\\/.test(path)
|
||||||
|
}
|
73
src/server/features/signatureHelp.ts
Normal file
73
src/server/features/signatureHelp.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Position, SignatureHelp, SignatureInformation, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { SignatureHelpProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as Previewer from '../utils/previewer'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptSignatureHelpProvider implements SignatureHelpProvider {
|
||||||
|
public static readonly triggerCharacters = ['(', ',', '<']
|
||||||
|
|
||||||
|
public constructor(private readonly client: ITypeScriptServiceClient) { }
|
||||||
|
|
||||||
|
public async provideSignatureHelp(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<SignatureHelp | undefined> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const args: Proto.SignatureHelpRequestArgs = typeConverters.Position.toFileLocationRequestArgs(
|
||||||
|
filepath,
|
||||||
|
position
|
||||||
|
)
|
||||||
|
|
||||||
|
let info: Proto.SignatureHelpItems | undefined
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('signatureHelp', args, token)
|
||||||
|
info = response.body
|
||||||
|
if (!info) return undefined
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: SignatureHelp = {
|
||||||
|
activeSignature: info.selectedItemIndex,
|
||||||
|
activeParameter: this.getActiveParmeter(info),
|
||||||
|
signatures: info.items.map(signature => {
|
||||||
|
return this.convertSignature(signature)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private getActiveParmeter(info: Proto.SignatureHelpItems): number {
|
||||||
|
const activeSignature = info.items[info.selectedItemIndex]
|
||||||
|
if (activeSignature && activeSignature.isVariadic) {
|
||||||
|
return Math.min(info.argumentIndex, activeSignature.parameters.length - 1)
|
||||||
|
}
|
||||||
|
return info.argumentIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertSignature(item: Proto.SignatureHelpItem): SignatureInformation {
|
||||||
|
return {
|
||||||
|
label: Previewer.plain(item.prefixDisplayParts).replace(/\($/, ''),
|
||||||
|
documentation: Previewer.markdownDocumentation(
|
||||||
|
item.documentation,
|
||||||
|
item.tags.filter(x => x.name !== 'param')
|
||||||
|
),
|
||||||
|
parameters: item.parameters.map(p => {
|
||||||
|
return {
|
||||||
|
label: Previewer.plain(p.displayParts),
|
||||||
|
documentation: Previewer.markdownDocumentation(p.documentation, [])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/server/features/tagCompletion.ts
Normal file
53
src/server/features/tagCompletion.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, CompletionContext, CompletionItem, Position, TextDocument } from 'vscode-languageserver-protocol'
|
||||||
|
import { CompletionItemProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
export default class TypeScriptTagCompletion implements CompletionItemProvider {
|
||||||
|
constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async provideCompletionItems(
|
||||||
|
document: TextDocument,
|
||||||
|
position: Position,
|
||||||
|
token: CancellationToken,
|
||||||
|
context: CompletionContext
|
||||||
|
): Promise<CompletionItem[] | undefined> {
|
||||||
|
const filepath = this.client.toPath(document.uri)
|
||||||
|
if (!filepath) return undefined
|
||||||
|
if (context.triggerCharacter != '>') {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position)
|
||||||
|
let body: Proto.TextInsertion | undefined
|
||||||
|
try {
|
||||||
|
const response = await this.client.execute('jsxClosingTag', args, token)
|
||||||
|
body = response && response.body
|
||||||
|
if (!body) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
return [this.getCompletion(body)]
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCompletion(body: Proto.TextInsertion): CompletionItem {
|
||||||
|
const completion = CompletionItem.create(body.newText)
|
||||||
|
completion.insertText = this.getTagSnippet(body) // tslint:disable-line
|
||||||
|
return completion
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTagSnippet(closingTag: Proto.TextInsertion): string {
|
||||||
|
let { newText, caretOffset } = closingTag
|
||||||
|
return newText.slice(0, caretOffset) + '$0' + newText.slice(caretOffset)
|
||||||
|
}
|
||||||
|
}
|
105
src/server/features/updatePathOnRename.ts
Normal file
105
src/server/features/updatePathOnRename.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { Disposable, TextDocument, WorkspaceEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import Uri from 'vscode-uri'
|
||||||
|
import { disposeAll, workspace } from 'coc.nvim'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
import FileConfigurationManager from './fileConfigurationManager'
|
||||||
|
|
||||||
|
function wait(ms: number): Promise<any> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve()
|
||||||
|
}, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UpdateImportsOnFileRenameHandler {
|
||||||
|
private disposables: Disposable[] = []
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
private readonly fileConfigurationManager: FileConfigurationManager,
|
||||||
|
languageId: string
|
||||||
|
) {
|
||||||
|
let glob = languageId == 'typescript' ? '**/*.ts' : '**/*.js'
|
||||||
|
const watcher = workspace.createFileSystemWatcher(glob)
|
||||||
|
this.disposables.push(watcher)
|
||||||
|
watcher.onDidRename(e => {
|
||||||
|
this.doRename(e.oldUri, e.newUri).catch(e => {
|
||||||
|
client.logger.error(e.message)
|
||||||
|
})
|
||||||
|
}, null, this.disposables)
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doRename(
|
||||||
|
oldResource: Uri,
|
||||||
|
newResource: Uri
|
||||||
|
): Promise<void> {
|
||||||
|
if (oldResource.scheme !== 'file' || newResource.scheme !== 'file') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const targetFile = newResource.fsPath
|
||||||
|
const oldFile = oldResource.fsPath
|
||||||
|
await workspace.openResource(newResource.toString())
|
||||||
|
// Make sure TS knows about file
|
||||||
|
await wait(100)
|
||||||
|
|
||||||
|
let document = workspace.getDocument(newResource.toString())
|
||||||
|
if (!document) return
|
||||||
|
|
||||||
|
const edits = await this.getEditsForFileRename(
|
||||||
|
document.textDocument,
|
||||||
|
oldFile,
|
||||||
|
targetFile,
|
||||||
|
)
|
||||||
|
if (!edits) return
|
||||||
|
|
||||||
|
if (await this.promptUser(newResource)) {
|
||||||
|
await workspace.applyEdit(edits)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async promptUser(newResource: Uri): Promise<boolean> {
|
||||||
|
const res = await workspace.nvim.call('coc#util#prompt_confirm', [`Update imports for moved file: ${newResource.fsPath} ?`])
|
||||||
|
return res == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getEditsForFileRename(document: TextDocument, oldFile: string, newFile: string): Promise<WorkspaceEdit> {
|
||||||
|
await this.fileConfigurationManager.ensureConfigurationForDocument(document)
|
||||||
|
const args: Proto.GetEditsForFileRenameRequestArgs = {
|
||||||
|
oldFilePath: oldFile,
|
||||||
|
newFilePath: newFile
|
||||||
|
}
|
||||||
|
const response = await this.client.execute('getEditsForFileRename', args)
|
||||||
|
if (!response || !response.body) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const edits: Proto.FileCodeEdits[] = []
|
||||||
|
for (const edit of response.body) {
|
||||||
|
// Workaround for https://github.com/Microsoft/vscode/issues/52675
|
||||||
|
if ((edit as Proto.FileCodeEdits).fileName.match(
|
||||||
|
/[\/\\]node_modules[\/\\]/gi
|
||||||
|
)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (const change of (edit as Proto.FileCodeEdits).textChanges) {
|
||||||
|
if (change.newText.match(/\/node_modules\//gi)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
edits.push(edit)
|
||||||
|
}
|
||||||
|
return typeConverters.WorkspaceEdit.fromFileCodeEdits(this.client, edits)
|
||||||
|
}
|
||||||
|
}
|
172
src/server/features/watchBuild.ts
Normal file
172
src/server/features/watchBuild.ts
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
import { DiagnosticCollection, disposeAll, Document, languages, workspace } from 'coc.nvim'
|
||||||
|
import { Command, CommandManager } from 'coc.nvim/lib/commands'
|
||||||
|
import { resolveRoot } from '../utils/fs'
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { Diagnostic, DiagnosticSeverity, Disposable, Range } from 'vscode-languageserver-protocol'
|
||||||
|
import Uri from 'vscode-uri'
|
||||||
|
|
||||||
|
const TSC = './node_modules/.bin/tsc'
|
||||||
|
const countRegex = /Found\s(\d+)\serror/
|
||||||
|
const startRegex = /File\s+change\s+detected/
|
||||||
|
const errorRegex = /^(.+):(\d+):(\d+)\s-\s(\w+)\s+[A-Za-z]+(\d+):\s+(.*)$/
|
||||||
|
|
||||||
|
enum TscStatus {
|
||||||
|
INIT,
|
||||||
|
COMPILING,
|
||||||
|
RUNNING,
|
||||||
|
ERROR,
|
||||||
|
}
|
||||||
|
|
||||||
|
class WatchCommand implements Command {
|
||||||
|
public readonly id: string = 'tsserver.watchBuild'
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private collection: DiagnosticCollection
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
private setStatus(state: TscStatus): void {
|
||||||
|
let s = 'init'
|
||||||
|
switch (state) {
|
||||||
|
case TscStatus.COMPILING:
|
||||||
|
s = 'compiling'
|
||||||
|
break
|
||||||
|
case TscStatus.RUNNING:
|
||||||
|
s = 'running'
|
||||||
|
break
|
||||||
|
case TscStatus.ERROR:
|
||||||
|
s = 'error'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
workspace.nvim.setVar('tsc_status', s, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async execute(): Promise<void> {
|
||||||
|
let docs = workspace.documents
|
||||||
|
let idx = docs.findIndex(doc => doc.uri.indexOf(TSC) !== -1)
|
||||||
|
if (idx !== -1) return
|
||||||
|
let document = await workspace.document
|
||||||
|
let fsPath = Uri.parse(document.uri).fsPath
|
||||||
|
let cwd = path.dirname(fsPath)
|
||||||
|
let dir = resolveRoot(cwd, ['node_modules'])
|
||||||
|
if (dir) {
|
||||||
|
let file = path.join(dir, 'node_modules/.bin/tsc')
|
||||||
|
if (!fs.existsSync(file)) dir = null
|
||||||
|
}
|
||||||
|
if (!dir) {
|
||||||
|
workspace.showMessage('typescript module not found!', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let configRoot = resolveRoot(cwd, ['tsconfig.json'])
|
||||||
|
if (!configRoot) {
|
||||||
|
workspace.showMessage('tsconfig.json not found!', 'error')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let configPath = path.relative(dir, path.join(configRoot, 'tsconfig.json'))
|
||||||
|
let cmd = `${TSC} -p ${configPath} --watch true`
|
||||||
|
await workspace.nvim.call('coc#util#open_terminal', {
|
||||||
|
keepfocus: 1,
|
||||||
|
cwd: dir,
|
||||||
|
cmd
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async onTerminalCreated(doc: Document): Promise<void> {
|
||||||
|
let entries: Map<string, Diagnostic[]> = new Map()
|
||||||
|
let cwd = await doc.getcwd()
|
||||||
|
if (!cwd) return
|
||||||
|
let uris = new Set()
|
||||||
|
this.setStatus(TscStatus.RUNNING)
|
||||||
|
let parseLine = (line: string): void => {
|
||||||
|
if (startRegex.test(line)) {
|
||||||
|
this.setStatus(TscStatus.COMPILING)
|
||||||
|
entries = new Map()
|
||||||
|
} else if (errorRegex.test(line)) {
|
||||||
|
let ms = line.match(errorRegex)
|
||||||
|
let severity = /error/.test(ms[4]) ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning
|
||||||
|
let lnum = Number(ms[2]) - 1
|
||||||
|
let character = Number(ms[3]) - 1
|
||||||
|
let range = Range.create(lnum, character, lnum, character)
|
||||||
|
let uri = Uri.file(path.join(cwd, ms[1])).toString()
|
||||||
|
let diagnostics = entries.get(uri) || []
|
||||||
|
diagnostics.push(Diagnostic.create(range, ms[6], severity, ms[5], 'tsc'))
|
||||||
|
entries.set(uri, diagnostics)
|
||||||
|
} else if (countRegex.test(line)) {
|
||||||
|
let ms = line.match(countRegex)
|
||||||
|
if (ms[1] == '0') {
|
||||||
|
entries = new Map()
|
||||||
|
this.setStatus(TscStatus.RUNNING)
|
||||||
|
this.collection.clear()
|
||||||
|
uris = new Set()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.setStatus(TscStatus.ERROR)
|
||||||
|
for (let [key, value] of entries.entries()) {
|
||||||
|
this.collection.set(key, value)
|
||||||
|
}
|
||||||
|
for (let uri of uris) {
|
||||||
|
if (!entries.has(uri)) {
|
||||||
|
this.collection.set(uri, [])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uris = new Set(entries.keys())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let line of doc.content.split('\n')) {
|
||||||
|
parseLine(line)
|
||||||
|
}
|
||||||
|
doc.onDocumentDetach(() => {
|
||||||
|
entries = new Map()
|
||||||
|
this.setStatus(TscStatus.INIT)
|
||||||
|
this.collection.clear()
|
||||||
|
})
|
||||||
|
doc.onDocumentChange(e => {
|
||||||
|
let { contentChanges } = e
|
||||||
|
for (let change of contentChanges) {
|
||||||
|
let lines = change.text.split('\n')
|
||||||
|
for (let line of lines) {
|
||||||
|
parseLine(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class WatchProject implements Disposable {
|
||||||
|
private disposables: Disposable[] = []
|
||||||
|
public constructor(
|
||||||
|
commandManager: CommandManager
|
||||||
|
) {
|
||||||
|
let collection = languages.createDiagnosticCollection('tsc')
|
||||||
|
let cmd = new WatchCommand(collection)
|
||||||
|
commandManager.register(cmd)
|
||||||
|
this.disposables.push(Disposable.create(() => {
|
||||||
|
commandManager.unregister(cmd.id)
|
||||||
|
}))
|
||||||
|
workspace.documents.forEach(doc => {
|
||||||
|
let { uri } = doc
|
||||||
|
if (this.isTscBuffer(uri)) {
|
||||||
|
cmd.onTerminalCreated(doc).catch(_e => {
|
||||||
|
// noop
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
workspace.onDidOpenTextDocument(doc => {
|
||||||
|
let { uri } = doc
|
||||||
|
if (this.isTscBuffer(uri)) {
|
||||||
|
cmd.onTerminalCreated(workspace.getDocument(uri)).catch(_e => {
|
||||||
|
// noop
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, this, this.disposables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private isTscBuffer(uri: string): boolean {
|
||||||
|
return uri.startsWith('term://') && uri.indexOf(TSC) !== -1
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
}
|
||||||
|
}
|
96
src/server/features/workspaceSymbols.ts
Normal file
96
src/server/features/workspaceSymbols.ts
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Range, SymbolInformation, SymbolKind } from 'vscode-languageserver-protocol'
|
||||||
|
import { WorkspaceSymbolProvider } from 'coc.nvim/lib/provider'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from '../utils/typeConverters'
|
||||||
|
|
||||||
|
function getSymbolKind(item: Proto.NavtoItem): SymbolKind {
|
||||||
|
switch (item.kind) {
|
||||||
|
case 'method':
|
||||||
|
return SymbolKind.Method
|
||||||
|
case 'enum':
|
||||||
|
return SymbolKind.Enum
|
||||||
|
case 'function':
|
||||||
|
return SymbolKind.Function
|
||||||
|
case 'class':
|
||||||
|
return SymbolKind.Class
|
||||||
|
case 'interface':
|
||||||
|
return SymbolKind.Interface
|
||||||
|
case 'var':
|
||||||
|
return SymbolKind.Variable
|
||||||
|
default:
|
||||||
|
return SymbolKind.Variable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbolProvider {
|
||||||
|
public constructor(
|
||||||
|
private readonly client: ITypeScriptServiceClient,
|
||||||
|
private readonly languageIds: string[]
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public async provideWorkspaceSymbols(
|
||||||
|
search: string,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<SymbolInformation[]> {
|
||||||
|
const uri = this.getUri()
|
||||||
|
if (!uri) return []
|
||||||
|
|
||||||
|
const filepath = this.client.toPath(uri)
|
||||||
|
if (!filepath) return []
|
||||||
|
|
||||||
|
const args: Proto.NavtoRequestArgs = {
|
||||||
|
file: filepath,
|
||||||
|
searchValue: search
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await this.client.execute('navto', args, token)
|
||||||
|
if (!response.body) return []
|
||||||
|
|
||||||
|
const result: SymbolInformation[] = []
|
||||||
|
for (const item of response.body) {
|
||||||
|
if (!item.containerName && item.kind === 'alias') {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const label = TypeScriptWorkspaceSymbolProvider.getLabel(item)
|
||||||
|
const range: Range = {
|
||||||
|
start: typeConverters.Position.fromLocation(item.start),
|
||||||
|
end: typeConverters.Position.fromLocation(item.end),
|
||||||
|
}
|
||||||
|
const symbolInfo = SymbolInformation.create(
|
||||||
|
label,
|
||||||
|
getSymbolKind(item),
|
||||||
|
range,
|
||||||
|
this.client.toResource(item.file))
|
||||||
|
|
||||||
|
result.push(symbolInfo)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getLabel(item: Proto.NavtoItem): string {
|
||||||
|
let label = item.name
|
||||||
|
if (item.kind === 'method' || item.kind === 'function') {
|
||||||
|
label += '()'
|
||||||
|
}
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
private getUri(): string {
|
||||||
|
// typescript wants to have a resource even when asking
|
||||||
|
// general questions so we check the active editor. If this
|
||||||
|
// doesn't match we take the first TS document.
|
||||||
|
const documents = workspace.textDocuments
|
||||||
|
for (const document of documents) {
|
||||||
|
if (this.languageIds.indexOf(document.languageId) >= 0) {
|
||||||
|
return document.uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
94
src/server/index.ts
Normal file
94
src/server/index.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import { disposeAll, IServiceProvider, ServiceStat, workspace, WorkspaceConfiguration } from 'coc.nvim'
|
||||||
|
import { Disposable, DocumentSelector, Emitter, Event } from 'vscode-languageserver-protocol'
|
||||||
|
import URI from 'vscode-uri'
|
||||||
|
import TypeScriptServiceClientHost from './typescriptServiceClientHost'
|
||||||
|
import { LanguageDescription, standardLanguageDescriptions } from './utils/languageDescription'
|
||||||
|
|
||||||
|
function wait(ms: number): Promise<any> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve()
|
||||||
|
}, ms)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TsserverService implements IServiceProvider {
|
||||||
|
public id = 'tsserver'
|
||||||
|
public name = 'tsserver'
|
||||||
|
public enable: boolean
|
||||||
|
// supported language types
|
||||||
|
public selector: DocumentSelector
|
||||||
|
public state = ServiceStat.Initial
|
||||||
|
public clientHost: TypeScriptServiceClientHost
|
||||||
|
private _onDidServiceReady = new Emitter<void>()
|
||||||
|
public readonly onServiceReady: Event<void> = this._onDidServiceReady.event
|
||||||
|
private readonly disposables: Disposable[] = []
|
||||||
|
private descriptions: LanguageDescription[] = []
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
const config = workspace.getConfiguration('tsserver')
|
||||||
|
const enableJavascript = !!config.get<boolean>('enableJavascript')
|
||||||
|
this.enable = config.get<boolean>('enable')
|
||||||
|
this.descriptions = standardLanguageDescriptions.filter(o => {
|
||||||
|
return enableJavascript ? true : o.id != 'javascript'
|
||||||
|
})
|
||||||
|
this.selector = this.descriptions.reduce((arr, c) => {
|
||||||
|
return arr.concat(c.modeIds)
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
public get config(): WorkspaceConfiguration {
|
||||||
|
return workspace.getConfiguration('tsserver')
|
||||||
|
}
|
||||||
|
|
||||||
|
public start(): Promise<void> {
|
||||||
|
this.clientHost = new TypeScriptServiceClientHost(this.descriptions)
|
||||||
|
this.disposables.push(this.clientHost)
|
||||||
|
Object.defineProperty(this, 'state', {
|
||||||
|
get: () => {
|
||||||
|
return this.clientHost.serviceClient.state
|
||||||
|
}
|
||||||
|
})
|
||||||
|
let client = this.clientHost.serviceClient
|
||||||
|
return new Promise(resolve => {
|
||||||
|
let started = false
|
||||||
|
client.onTsServerStarted(() => {
|
||||||
|
this._onDidServiceReady.fire(void 0)
|
||||||
|
this.ensureConfiguration() // tslint:disable-line
|
||||||
|
if (!started) {
|
||||||
|
started = true
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ensureConfiguration(): Promise<void> {
|
||||||
|
if (!this.clientHost) return
|
||||||
|
let document = await workspace.document
|
||||||
|
await wait(100)
|
||||||
|
|
||||||
|
let uri = URI.parse(document.uri)
|
||||||
|
let language = this.clientHost.findLanguage(uri)
|
||||||
|
if (!language) return
|
||||||
|
await language.fileConfigurationManager.ensureConfigurationForDocument(document.textDocument)
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
}
|
||||||
|
|
||||||
|
public async restart(): Promise<void> {
|
||||||
|
if (!this.clientHost) return
|
||||||
|
let client = this.clientHost.serviceClient
|
||||||
|
await client.restartTsServer()
|
||||||
|
}
|
||||||
|
|
||||||
|
public async stop(): Promise<void> {
|
||||||
|
if (!this.clientHost) return
|
||||||
|
this.clientHost.reset()
|
||||||
|
let client = this.clientHost.serviceClient
|
||||||
|
await client.stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
349
src/server/languageProvider.ts
Normal file
349
src/server/languageProvider.ts
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { Diagnostic, Disposable } from 'vscode-languageserver-protocol'
|
||||||
|
import Uri from 'vscode-uri'
|
||||||
|
import { workspace, commands, events, languages, DiagnosticKind, ServiceStat, disposeAll } from 'coc.nvim'
|
||||||
|
import { CachedNavTreeResponse } from './features/baseCodeLensProvider'
|
||||||
|
import BufferSyncSupport from './features/bufferSyncSupport'
|
||||||
|
import CompletionItemProvider from './features/completionItemProvider'
|
||||||
|
import DefinitionProvider from './features/definitionProvider'
|
||||||
|
import { DiagnosticsManager } from './features/diagnostics'
|
||||||
|
import DirectiveCommentCompletionProvider from './features/directiveCommentCompletions'
|
||||||
|
import DocumentHighlight from './features/documentHighlight'
|
||||||
|
import DocumentSymbolProvider from './features/documentSymbol'
|
||||||
|
import FileConfigurationManager from './features/fileConfigurationManager'
|
||||||
|
import Folding from './features/folding'
|
||||||
|
import FormattingProvider from './features/formatting'
|
||||||
|
import HoverProvider from './features/hover'
|
||||||
|
import ImplementationsCodeLensProvider from './features/implementationsCodeLens'
|
||||||
|
import OrganizeImportsProvider from './features/organizeImports'
|
||||||
|
// import TagCompletionProvider from './features/tagCompletion'
|
||||||
|
import QuickfixProvider from './features/quickfix'
|
||||||
|
import RefactorProvider from './features/refactor'
|
||||||
|
import ReferenceProvider from './features/references'
|
||||||
|
import ReferencesCodeLensProvider from './features/referencesCodeLens'
|
||||||
|
import RenameProvider from './features/rename'
|
||||||
|
import SignatureHelpProvider from './features/signatureHelp'
|
||||||
|
import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename'
|
||||||
|
import WatchBuild from './features/watchBuild'
|
||||||
|
import WorkspaceSymbolProvider from './features/workspaceSymbols'
|
||||||
|
import TypeScriptServiceClient from './typescriptServiceClient'
|
||||||
|
import API from './utils/api'
|
||||||
|
import { LanguageDescription } from './utils/languageDescription'
|
||||||
|
import TypingsStatus from './utils/typingsStatus'
|
||||||
|
|
||||||
|
const validateSetting = 'validate.enable'
|
||||||
|
const suggestionSetting = 'suggestionActions.enabled'
|
||||||
|
|
||||||
|
export default class LanguageProvider {
|
||||||
|
private readonly diagnosticsManager: DiagnosticsManager
|
||||||
|
private readonly bufferSyncSupport: BufferSyncSupport
|
||||||
|
public readonly fileConfigurationManager: FileConfigurationManager // tslint:disable-line
|
||||||
|
private _validate = true
|
||||||
|
private _enableSuggestionDiagnostics = true
|
||||||
|
private readonly disposables: Disposable[] = []
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public client: TypeScriptServiceClient,
|
||||||
|
private description: LanguageDescription,
|
||||||
|
typingsStatus: TypingsStatus
|
||||||
|
) {
|
||||||
|
this.fileConfigurationManager = new FileConfigurationManager(client)
|
||||||
|
this.bufferSyncSupport = new BufferSyncSupport(
|
||||||
|
client,
|
||||||
|
description.modeIds,
|
||||||
|
this._validate
|
||||||
|
)
|
||||||
|
this.diagnosticsManager = new DiagnosticsManager()
|
||||||
|
this.disposables.push(this.diagnosticsManager)
|
||||||
|
|
||||||
|
client.onTsServerStarted(async () => {
|
||||||
|
let document = await workspace.document
|
||||||
|
if (description.modeIds.indexOf(document.filetype) !== -1) {
|
||||||
|
this.fileConfigurationManager.ensureConfigurationForDocument(document.textDocument) // tslint:disable-line
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
events.on('BufEnter', bufnr => {
|
||||||
|
let doc = workspace.getDocument(bufnr)
|
||||||
|
if (!doc) return
|
||||||
|
if (description.modeIds.indexOf(doc.filetype) == -1) return
|
||||||
|
if (client.state !== ServiceStat.Running) return
|
||||||
|
this.fileConfigurationManager.ensureConfigurationForDocument(doc.textDocument) // tslint:disable-line
|
||||||
|
}, this, this.disposables)
|
||||||
|
|
||||||
|
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
|
||||||
|
|
||||||
|
let initialized = false
|
||||||
|
|
||||||
|
client.onTsServerStarted(() => { // tslint:disable-line
|
||||||
|
if (!initialized) {
|
||||||
|
initialized = true
|
||||||
|
this.registerProviders(client, typingsStatus)
|
||||||
|
this.bufferSyncSupport.listen()
|
||||||
|
} else {
|
||||||
|
this.reInitialize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
this.bufferSyncSupport.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private configurationChanged(): void {
|
||||||
|
const config = workspace.getConfiguration(this.id)
|
||||||
|
this.updateValidate(config.get(validateSetting, true))
|
||||||
|
this.updateSuggestionDiagnostics(config.get(suggestionSetting, true))
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerProviders(
|
||||||
|
client: TypeScriptServiceClient,
|
||||||
|
typingsStatus: TypingsStatus
|
||||||
|
): void {
|
||||||
|
let languageIds = this.description.modeIds
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerCompletionItemProvider(
|
||||||
|
`tsserver-${this.description.id}`,
|
||||||
|
'TSC',
|
||||||
|
languageIds,
|
||||||
|
new CompletionItemProvider(
|
||||||
|
client,
|
||||||
|
typingsStatus,
|
||||||
|
this.fileConfigurationManager,
|
||||||
|
this.description.id
|
||||||
|
),
|
||||||
|
CompletionItemProvider.triggerCharacters
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (this.client.apiVersion.gte(API.v230)) {
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerCompletionItemProvider(
|
||||||
|
`${this.description.id}-directive`,
|
||||||
|
'TSC',
|
||||||
|
languageIds,
|
||||||
|
new DirectiveCommentCompletionProvider(
|
||||||
|
client,
|
||||||
|
),
|
||||||
|
['@']
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let definitionProvider = new DefinitionProvider(client)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerDefinitionProvider(
|
||||||
|
languageIds,
|
||||||
|
definitionProvider
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerTypeDefinitionProvider(
|
||||||
|
languageIds,
|
||||||
|
definitionProvider
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerImplementationProvider(
|
||||||
|
languageIds,
|
||||||
|
definitionProvider
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerReferencesProvider(
|
||||||
|
languageIds,
|
||||||
|
new ReferenceProvider(client)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerHoverProvider(
|
||||||
|
languageIds,
|
||||||
|
new HoverProvider(client))
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerDocumentHighlightProvider(languageIds, new DocumentHighlight(this.client))
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerSignatureHelpProvider(
|
||||||
|
languageIds,
|
||||||
|
new SignatureHelpProvider(client))
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerDocumentSymbolProvider(
|
||||||
|
languageIds,
|
||||||
|
new DocumentSymbolProvider(client))
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerWorkspaceSymbolProvider(
|
||||||
|
languageIds,
|
||||||
|
new WorkspaceSymbolProvider(client, languageIds))
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerRenameProvider(
|
||||||
|
languageIds,
|
||||||
|
new RenameProvider(client))
|
||||||
|
)
|
||||||
|
let formatProvider = new FormattingProvider(client, this.fileConfigurationManager)
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerDocumentFormatProvider(languageIds, formatProvider)
|
||||||
|
)
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerDocumentRangeFormatProvider(languageIds, formatProvider)
|
||||||
|
)
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerOnTypeFormattingEditProvider(languageIds, formatProvider, [';', '}', '\n'])
|
||||||
|
)
|
||||||
|
|
||||||
|
// this.disposables.push(
|
||||||
|
// new ProjectError(client, commandManager)
|
||||||
|
// )
|
||||||
|
|
||||||
|
if (this.client.apiVersion.gte(API.v280)) {
|
||||||
|
this.disposables.push(
|
||||||
|
new OrganizeImportsProvider(client, commands, this.fileConfigurationManager, this.description.id)
|
||||||
|
)
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerFoldingRangeProvider(languageIds, new Folding(this.client))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
let { fileConfigurationManager } = this
|
||||||
|
let conf = fileConfigurationManager.getLanguageConfiguration(this.id)
|
||||||
|
|
||||||
|
if (this.client.apiVersion.gte(API.v290)
|
||||||
|
&& conf.get<boolean>('updateImportsOnFileMove.enable')) {
|
||||||
|
this.disposables.push(
|
||||||
|
new UpdateImportsOnFileRenameHandler(client, this.fileConfigurationManager, this.id)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.client.apiVersion.gte(API.v240)) {
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerCodeActionProvider(
|
||||||
|
languageIds,
|
||||||
|
new RefactorProvider(client, this.fileConfigurationManager)))
|
||||||
|
}
|
||||||
|
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerCodeActionProvider(
|
||||||
|
languageIds,
|
||||||
|
new QuickfixProvider(client, this.diagnosticsManager, this.bufferSyncSupport)))
|
||||||
|
let cachedResponse = new CachedNavTreeResponse()
|
||||||
|
if (this.client.apiVersion.gte(API.v206)
|
||||||
|
&& conf.get<boolean>('referencesCodeLens.enable')) {
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerCodeLensProvider(
|
||||||
|
languageIds,
|
||||||
|
new ReferencesCodeLensProvider(client, cachedResponse)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.client.apiVersion.gte(API.v220)
|
||||||
|
&& conf.get<boolean>('implementationsCodeLens.enable')) {
|
||||||
|
this.disposables.push(
|
||||||
|
languages.registerCodeLensProvider(
|
||||||
|
languageIds,
|
||||||
|
new ImplementationsCodeLensProvider(client, cachedResponse)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.description.id == 'typescript') {
|
||||||
|
this.disposables.push(
|
||||||
|
new WatchBuild(commands)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (this.client.apiVersion.gte(API.v300)) {
|
||||||
|
// this.disposables.push(
|
||||||
|
// languages.registerCompletionItemProvider(
|
||||||
|
// `tsserver-${this.description.id}-tag`,
|
||||||
|
// 'TSC',
|
||||||
|
// languageIds,
|
||||||
|
// new TagCompletionProvider(client),
|
||||||
|
// ['>']
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
public handles(resource: Uri): boolean {
|
||||||
|
let doc = workspace.getDocument(resource.toString())
|
||||||
|
let { modeIds } = this.description
|
||||||
|
if (doc && modeIds.indexOf(doc.filetype) !== -1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
let str = resource.toString()
|
||||||
|
if (this.id === 'typescript' && /\.ts(x)?$/.test(str)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (this.id === 'javascript' && /\.js(x)?$/.test(str)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private get id(): string { // tslint:disable-line
|
||||||
|
return this.description.id
|
||||||
|
}
|
||||||
|
|
||||||
|
public get diagnosticSource(): string {
|
||||||
|
return this.description.diagnosticSource
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateValidate(value: boolean): void {
|
||||||
|
if (this._validate === value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._validate = value
|
||||||
|
this.bufferSyncSupport.validate = value
|
||||||
|
this.diagnosticsManager.validate = value
|
||||||
|
if (value) {
|
||||||
|
this.triggerAllDiagnostics()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateSuggestionDiagnostics(value: boolean): void {
|
||||||
|
if (this._enableSuggestionDiagnostics === value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._enableSuggestionDiagnostics = value
|
||||||
|
this.diagnosticsManager.enableSuggestions = value
|
||||||
|
if (value) {
|
||||||
|
this.triggerAllDiagnostics()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public reInitialize(): void {
|
||||||
|
this.diagnosticsManager.reInitialize()
|
||||||
|
this.bufferSyncSupport.reInitialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerAllDiagnostics(): void {
|
||||||
|
this.bufferSyncSupport.requestAllDiagnostics()
|
||||||
|
}
|
||||||
|
|
||||||
|
public diagnosticsReceived(
|
||||||
|
diagnosticsKind: DiagnosticKind,
|
||||||
|
file: Uri,
|
||||||
|
diagnostics: Diagnostic[]
|
||||||
|
): void {
|
||||||
|
this.diagnosticsManager.diagnosticsReceived(
|
||||||
|
diagnosticsKind,
|
||||||
|
file.toString(),
|
||||||
|
diagnostics
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
39
src/server/protocol.const.ts
Normal file
39
src/server/protocol.const.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
export class Kind {
|
||||||
|
public static readonly alias = 'alias'
|
||||||
|
public static readonly callSignature = 'call'
|
||||||
|
public static readonly class = 'class'
|
||||||
|
public static readonly const = 'const'
|
||||||
|
public static readonly constructorImplementation = 'constructor'
|
||||||
|
public static readonly constructSignature = 'construct'
|
||||||
|
public static readonly directory = 'directory'
|
||||||
|
public static readonly enum = 'enum'
|
||||||
|
public static readonly externalModuleName = 'external module name'
|
||||||
|
public static readonly file = 'file'
|
||||||
|
public static readonly function = 'function'
|
||||||
|
public static readonly indexSignature = 'index'
|
||||||
|
public static readonly interface = 'interface'
|
||||||
|
public static readonly keyword = 'keyword'
|
||||||
|
public static readonly let = 'let'
|
||||||
|
public static readonly localFunction = 'local function'
|
||||||
|
public static readonly localVariable = 'local var'
|
||||||
|
public static readonly memberFunction = 'method'
|
||||||
|
public static readonly memberGetAccessor = 'getter'
|
||||||
|
public static readonly memberSetAccessor = 'setter'
|
||||||
|
public static readonly memberVariable = 'property'
|
||||||
|
public static readonly module = 'module'
|
||||||
|
public static readonly primitiveType = 'primitive type'
|
||||||
|
public static readonly script = 'script'
|
||||||
|
public static readonly type = 'type'
|
||||||
|
public static readonly variable = 'var'
|
||||||
|
public static readonly warning = 'warning'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DiagnosticCategory {
|
||||||
|
public static readonly error = 'error'
|
||||||
|
public static readonly warning = 'warning'
|
||||||
|
public static readonly suggestion = 'suggestion'
|
||||||
|
}
|
2475
src/server/protocol.d.ts
vendored
Normal file
2475
src/server/protocol.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
298
src/server/schema.json
Normal file
298
src/server/schema.json
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema",
|
||||||
|
"properties": {
|
||||||
|
"tsserver.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable tsserver extension"
|
||||||
|
},
|
||||||
|
"tsserver.locale": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Locale of tsserver"
|
||||||
|
},
|
||||||
|
"tsserver.typingsCacheLocation": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Folder path for cache typings"
|
||||||
|
},
|
||||||
|
"tsserver.formatOnSave": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Format document on buffer will save"
|
||||||
|
},
|
||||||
|
"tsserver.orgnizeImportOnSave": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Orgnize import on buffer will save"
|
||||||
|
},
|
||||||
|
"tsserver.formatOnType": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Run format on type special characters."
|
||||||
|
},
|
||||||
|
"tsserver.enableJavascript": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Use tsserver for javascript files"
|
||||||
|
},
|
||||||
|
"tsserver.tsdk": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Directory contains tsserver.js, works for workspace only"
|
||||||
|
},
|
||||||
|
"tsserver.npm": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Executable path of npm for download typings"
|
||||||
|
},
|
||||||
|
"tsserver.log": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "off",
|
||||||
|
"enum": ["normal", "terse", "verbose", "off"],
|
||||||
|
"description": "Log level of tsserver"
|
||||||
|
},
|
||||||
|
"tsserver.trace.server": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "off",
|
||||||
|
"enum": ["off", "messages", "verbose"],
|
||||||
|
"description": "Trace level of tsserver"
|
||||||
|
},
|
||||||
|
"tserver.pluginNames": {
|
||||||
|
"type": "array",
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": "Module names of tsserver plugins"
|
||||||
|
},
|
||||||
|
"tsserver.pluginRoot": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "",
|
||||||
|
"description": "Folder contains tsserver plugins"
|
||||||
|
},
|
||||||
|
"tsserver.debugPort": {
|
||||||
|
"type": "number",
|
||||||
|
"description": "Debug port number of tsserver"
|
||||||
|
},
|
||||||
|
"tsserver.reportStyleChecksAsWarnings": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"tsserver.implicitProjectConfig.checkJs": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable checkJs for implicit project"
|
||||||
|
},
|
||||||
|
"tsserver.implicitProjectConfig.experimentalDecorators": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable experimentalDecorators for implicit project"
|
||||||
|
},
|
||||||
|
"tsserver.disableAutomaticTypeAcquisition": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Disable download of typings"
|
||||||
|
},
|
||||||
|
"typescript.updateImportsOnFileMove.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable update imports on file move."
|
||||||
|
},
|
||||||
|
"typescript.implementationsCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable codeLens for implementations"
|
||||||
|
},
|
||||||
|
"typescript.referencesCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable codeLens for references"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.useCodeSnippetsOnMethodSuggest": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable snippet for method suggestion"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.nameSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Complete for warning type of tsserver"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.autoImportSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Enable auto import suggestions for completion"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.commaAfterImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Add comma after import"
|
||||||
|
},
|
||||||
|
"typescript.preferences.completion.moduleExports": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true,
|
||||||
|
"description": "Include completion for module.exports"
|
||||||
|
},
|
||||||
|
"typescript.preferences.importModuleSpecifier": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "non-relative",
|
||||||
|
"enum": ["non-relative", "relative"]
|
||||||
|
},
|
||||||
|
"typescript.preferences.suggestionActions.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.preferences.quoteStyle": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "single",
|
||||||
|
"enum": ["single", "double"]
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterCommaDelimiter": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterConstructor": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterSemicolonInForStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceBeforeAndAfterBinaryOperators": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterKeywordsInControlFlowStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceBeforeFunctionParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.insertSpaceAfterTypeAssertion": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.placeOpenBraceOnNewLineForFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"typescript.format.placeOpenBraceOnNewLineForControlBlocks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.updateImportsOnFileMove.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.implementationsCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.referencesCodeLens.enable": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.useCodeSnippetsOnMethodSuggest": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.nameSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.autoImportSuggestions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.importModuleSpecifier": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "non-relative",
|
||||||
|
"enum": ["non-relative", "relative"]
|
||||||
|
},
|
||||||
|
"javascript.preferences.suggestionActions.enabled": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.completion.commaAfterImport": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.preferences.quoteStyle": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "single",
|
||||||
|
"enum": ["single", "double"]
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterCommaDelimiter": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterConstructor": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterSemicolonInForStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceBeforeAndAfterBinaryOperators": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterKeywordsInControlFlowStatements": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterFunctionKeywordForAnonymousFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceBeforeFunctionParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.insertSpaceAfterTypeAssertion": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.placeOpenBraceOnNewLineForFunctions": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"javascript.format.placeOpenBraceOnNewLineForControlBlocks": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
215
src/server/typescriptService.ts
Normal file
215
src/server/typescriptService.ts
Normal file
|
@ -0,0 +1,215 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CancellationToken, Event } from 'vscode-languageserver-protocol'
|
||||||
|
import Uri from 'vscode-uri'
|
||||||
|
import * as Proto from './protocol'
|
||||||
|
import API from './utils/api'
|
||||||
|
import { TypeScriptServiceConfiguration } from './utils/configuration'
|
||||||
|
import Logger from './utils/logger'
|
||||||
|
|
||||||
|
export interface TypeScriptServerPlugin {
|
||||||
|
readonly path: string
|
||||||
|
readonly name: string
|
||||||
|
readonly languages: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITypeScriptServiceClient {
|
||||||
|
apiVersion: API
|
||||||
|
configuration: TypeScriptServiceConfiguration
|
||||||
|
onTsServerStarted: Event<API>
|
||||||
|
onProjectLanguageServiceStateChanged: Event<Proto.ProjectLanguageServiceStateEventBody>
|
||||||
|
onDidBeginInstallTypings: Event<Proto.BeginInstallTypesEventBody>
|
||||||
|
onDidEndInstallTypings: Event<Proto.EndInstallTypesEventBody>
|
||||||
|
onTypesInstallerInitializationFailed: Event<Proto.TypesInstallerInitializationFailedEventBody>
|
||||||
|
readonly logger: Logger
|
||||||
|
|
||||||
|
normalizePath(resource: Uri): string | null
|
||||||
|
asUrl(filepath: string): Uri
|
||||||
|
toPath(uri: string): string
|
||||||
|
toResource(path: string): string
|
||||||
|
|
||||||
|
execute(
|
||||||
|
command: 'configure',
|
||||||
|
args: Proto.ConfigureRequestArguments,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.ConfigureResponse>
|
||||||
|
execute(
|
||||||
|
command: 'open',
|
||||||
|
args: Proto.OpenRequestArgs,
|
||||||
|
expectedResult: boolean,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'close',
|
||||||
|
args: Proto.FileRequestArgs,
|
||||||
|
expectedResult: boolean,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'change',
|
||||||
|
args: Proto.ChangeRequestArgs,
|
||||||
|
expectedResult: boolean,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'geterr',
|
||||||
|
args: Proto.GeterrRequestArgs,
|
||||||
|
expectedResult: boolean,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'geterrForProject',
|
||||||
|
args: Proto.GeterrForProjectRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'quickinfo',
|
||||||
|
args: Proto.FileLocationRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.QuickInfoResponse>
|
||||||
|
execute(
|
||||||
|
command: 'completions',
|
||||||
|
args: Proto.CompletionsRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.CompletionsResponse> // tslint:disable-line
|
||||||
|
execute(
|
||||||
|
command: 'completionEntryDetails',
|
||||||
|
args: Proto.CompletionDetailsRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.CompletionDetailsResponse>
|
||||||
|
execute(
|
||||||
|
command: 'signatureHelp',
|
||||||
|
args: Proto.SignatureHelpRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.SignatureHelpResponse>
|
||||||
|
execute(
|
||||||
|
command: 'definition',
|
||||||
|
args: Proto.FileLocationRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.DefinitionResponse>
|
||||||
|
execute(
|
||||||
|
command: 'implementation',
|
||||||
|
args: Proto.FileLocationRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.ImplementationResponse>
|
||||||
|
execute(
|
||||||
|
command: 'typeDefinition',
|
||||||
|
args: Proto.FileLocationRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.TypeDefinitionResponse>
|
||||||
|
execute(
|
||||||
|
command: 'references',
|
||||||
|
args: Proto.FileLocationRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.ReferencesResponse>
|
||||||
|
execute(
|
||||||
|
command: 'navto',
|
||||||
|
args: Proto.NavtoRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.NavtoResponse>
|
||||||
|
execute(
|
||||||
|
command: 'navbar',
|
||||||
|
args: Proto.FileRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.NavBarResponse>
|
||||||
|
execute(
|
||||||
|
command: 'format',
|
||||||
|
args: Proto.FormatRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.FormatResponse>
|
||||||
|
execute(
|
||||||
|
command: 'formatonkey',
|
||||||
|
args: Proto.FormatOnKeyRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.FormatResponse>
|
||||||
|
execute(
|
||||||
|
command: 'rename',
|
||||||
|
args: Proto.RenameRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.RenameResponse>
|
||||||
|
execute(
|
||||||
|
command: 'projectInfo',
|
||||||
|
args: Proto.ProjectInfoRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.ProjectInfoResponse>
|
||||||
|
execute(
|
||||||
|
command: 'reloadProjects',
|
||||||
|
args: any,
|
||||||
|
expectedResult: boolean,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'reload',
|
||||||
|
args: Proto.ReloadRequestArgs,
|
||||||
|
expectedResult: boolean,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'compilerOptionsForInferredProjects',
|
||||||
|
args: Proto.SetCompilerOptionsForInferredProjectsArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
execute(
|
||||||
|
command: 'navtree',
|
||||||
|
args: Proto.FileRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.NavTreeResponse>
|
||||||
|
execute(
|
||||||
|
command: 'getCodeFixes',
|
||||||
|
args: Proto.CodeFixRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.GetCodeFixesResponse>
|
||||||
|
execute(
|
||||||
|
command: 'getSupportedCodeFixes',
|
||||||
|
args: null,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.GetSupportedCodeFixesResponse>
|
||||||
|
execute(
|
||||||
|
command: 'getCombinedCodeFix',
|
||||||
|
args: Proto.GetCombinedCodeFixRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.GetCombinedCodeFixResponse>
|
||||||
|
execute(
|
||||||
|
command: 'docCommentTemplate',
|
||||||
|
args: Proto.FileLocationRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.DocCommandTemplateResponse>
|
||||||
|
execute(
|
||||||
|
command: 'getApplicableRefactors',
|
||||||
|
args: Proto.GetApplicableRefactorsRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.GetApplicableRefactorsResponse>
|
||||||
|
execute(
|
||||||
|
command: 'getEditsForRefactor',
|
||||||
|
args: Proto.GetEditsForRefactorRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.GetEditsForRefactorResponse>
|
||||||
|
execute(
|
||||||
|
command: 'getEditsForFileRename',
|
||||||
|
args: Proto.GetEditsForFileRenameRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.GetEditsForFileRenameResponse>
|
||||||
|
execute(
|
||||||
|
command: 'applyCodeActionCommand',
|
||||||
|
args: Proto.ApplyCodeActionCommandRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.ApplyCodeActionCommandResponse>
|
||||||
|
execute(
|
||||||
|
command: 'organizeImports',
|
||||||
|
args: Proto.OrganizeImportsRequestArgs,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<Proto.OrganizeImportsResponse>
|
||||||
|
execute(
|
||||||
|
command: 'getOutliningSpans',
|
||||||
|
args: Proto.FileRequestArgs,
|
||||||
|
token: CancellationToken
|
||||||
|
): Promise<Proto.OutliningSpansResponse>
|
||||||
|
execute(
|
||||||
|
command: string,
|
||||||
|
args: any,
|
||||||
|
expectedResult: boolean | CancellationToken,
|
||||||
|
token?: CancellationToken
|
||||||
|
): Promise<any>
|
||||||
|
}
|
834
src/server/typescriptServiceClient.ts
Normal file
834
src/server/typescriptServiceClient.ts
Normal file
|
@ -0,0 +1,834 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 fs from 'fs'
|
||||||
|
import os from 'os'
|
||||||
|
import path from 'path'
|
||||||
|
import { CancellationToken, Disposable, Emitter, Event } from 'vscode-languageserver-protocol'
|
||||||
|
import Uri from 'vscode-uri'
|
||||||
|
import which from 'which'
|
||||||
|
import { DiagnosticKind, ServiceStat, workspace, disposeAll } from 'coc.nvim'
|
||||||
|
import FileConfigurationManager from './features/fileConfigurationManager'
|
||||||
|
import * as Proto from './protocol'
|
||||||
|
import { ITypeScriptServiceClient } from './typescriptService'
|
||||||
|
import API from './utils/api'
|
||||||
|
import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration'
|
||||||
|
import Logger from './utils/logger'
|
||||||
|
import { fork, getTempFile, IForkOptions, makeRandomHexString } from './utils/process'
|
||||||
|
import Tracer from './utils/tracer'
|
||||||
|
import { inferredProjectConfig } from './utils/tsconfig'
|
||||||
|
import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionProvider'
|
||||||
|
import { ICallback, Reader } from './utils/wireProtocol'
|
||||||
|
|
||||||
|
interface CallbackItem {
|
||||||
|
c: (value: any) => void
|
||||||
|
e: (err: any) => void
|
||||||
|
start: number
|
||||||
|
}
|
||||||
|
|
||||||
|
class CallbackMap {
|
||||||
|
private readonly callbacks: Map<number, CallbackItem> = new Map()
|
||||||
|
public pendingResponses = 0
|
||||||
|
|
||||||
|
public destroy(e: any): void {
|
||||||
|
for (const callback of this.callbacks.values()) {
|
||||||
|
callback.e(e)
|
||||||
|
}
|
||||||
|
this.callbacks.clear()
|
||||||
|
this.pendingResponses = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
public add(seq: number, callback: CallbackItem): void {
|
||||||
|
this.callbacks.set(seq, callback)
|
||||||
|
++this.pendingResponses
|
||||||
|
}
|
||||||
|
|
||||||
|
public fetch(seq: number): CallbackItem | undefined {
|
||||||
|
const callback = this.callbacks.get(seq)
|
||||||
|
this.delete(seq)
|
||||||
|
return callback
|
||||||
|
}
|
||||||
|
|
||||||
|
private delete(seq: number): void {
|
||||||
|
if (this.callbacks.delete(seq)) {
|
||||||
|
--this.pendingResponses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RequestItem {
|
||||||
|
request: Proto.Request
|
||||||
|
callbacks: CallbackItem | null
|
||||||
|
}
|
||||||
|
|
||||||
|
class RequestQueue {
|
||||||
|
private queue: RequestItem[] = []
|
||||||
|
private sequenceNumber = 0
|
||||||
|
|
||||||
|
public get length(): number {
|
||||||
|
return this.queue.length
|
||||||
|
}
|
||||||
|
|
||||||
|
public push(item: RequestItem): void {
|
||||||
|
this.queue.push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
public shift(): RequestItem | undefined {
|
||||||
|
return this.queue.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
public tryCancelPendingRequest(seq: number): boolean {
|
||||||
|
for (let i = 0; i < this.queue.length; i++) {
|
||||||
|
if (this.queue[i].request.seq === seq) {
|
||||||
|
this.queue.splice(i, 1)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
public createRequest(command: string, args: any): Proto.Request {
|
||||||
|
return {
|
||||||
|
seq: this.sequenceNumber++,
|
||||||
|
type: 'request',
|
||||||
|
command,
|
||||||
|
arguments: args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ForkedTsServerProcess {
|
||||||
|
constructor(private childProcess: cp.ChildProcess) { }
|
||||||
|
|
||||||
|
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 {
|
||||||
|
readonly kind: DiagnosticKind
|
||||||
|
readonly resource: Uri
|
||||||
|
readonly diagnostics: Proto.Diagnostic[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
|
||||||
|
public state = ServiceStat.Initial
|
||||||
|
public readonly logger: Logger = new Logger()
|
||||||
|
private fileConfigurationManager: FileConfigurationManager
|
||||||
|
private pathSeparator: string
|
||||||
|
private tracer: Tracer
|
||||||
|
private _configuration: TypeScriptServiceConfiguration
|
||||||
|
private versionProvider: TypeScriptVersionProvider
|
||||||
|
private tsServerLogFile: string | null = null
|
||||||
|
private servicePromise: Thenable<ForkedTsServerProcess> | null
|
||||||
|
private lastError: Error | null
|
||||||
|
private lastStart: number
|
||||||
|
private numberRestarts: number
|
||||||
|
private cancellationPipeName: string | null = null
|
||||||
|
private requestQueue: RequestQueue
|
||||||
|
private callbacks: CallbackMap
|
||||||
|
private readonly _onTsServerStarted = new Emitter<API>()
|
||||||
|
private readonly _onProjectLanguageServiceStateChanged = new Emitter<Proto.ProjectLanguageServiceStateEventBody>()
|
||||||
|
private readonly _onDidBeginInstallTypings = new Emitter<Proto.BeginInstallTypesEventBody>()
|
||||||
|
private readonly _onDidEndInstallTypings = new Emitter<Proto.EndInstallTypesEventBody>()
|
||||||
|
private readonly _onTypesInstallerInitializationFailed = new Emitter<
|
||||||
|
Proto.TypesInstallerInitializationFailedEventBody
|
||||||
|
>()
|
||||||
|
private _apiVersion: API
|
||||||
|
private readonly disposables: Disposable[] = []
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.pathSeparator = path.sep
|
||||||
|
this.lastStart = Date.now()
|
||||||
|
this.servicePromise = null
|
||||||
|
this.lastError = null
|
||||||
|
this.numberRestarts = 0
|
||||||
|
this.fileConfigurationManager = new FileConfigurationManager(this)
|
||||||
|
this.requestQueue = new RequestQueue()
|
||||||
|
this.callbacks = new CallbackMap()
|
||||||
|
this._configuration = TypeScriptServiceConfiguration.loadFromWorkspace()
|
||||||
|
this.versionProvider = new TypeScriptVersionProvider(this._configuration)
|
||||||
|
this._apiVersion = API.defaultVersion
|
||||||
|
this.tracer = new Tracer(this.logger)
|
||||||
|
const onInstalled = name => {
|
||||||
|
if (name == 'typescript') {
|
||||||
|
this.restartTsServer().catch(e => {
|
||||||
|
this.logger.error(e.stack)
|
||||||
|
})
|
||||||
|
workspace.terminal.removeListener('installed', onInstalled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
workspace.terminal.on('installed', onInstalled)
|
||||||
|
this.disposables.push(Disposable.create(() => {
|
||||||
|
workspace.terminal.removeListener('installed', onInstalled)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDiagnosticsReceived = new Emitter<TsDiagnostics>()
|
||||||
|
public get onDiagnosticsReceived(): Event<TsDiagnostics> {
|
||||||
|
return this._onDiagnosticsReceived.event
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onConfigDiagnosticsReceived = new Emitter<Proto.ConfigFileDiagnosticEvent>()
|
||||||
|
public get onConfigDiagnosticsReceived(): Event<Proto.ConfigFileDiagnosticEvent> {
|
||||||
|
return this._onConfigDiagnosticsReceived.event
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onResendModelsRequested = new Emitter<void>()
|
||||||
|
public get onResendModelsRequested(): Event<void> {
|
||||||
|
return this._onResendModelsRequested.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public get configuration(): TypeScriptServiceConfiguration {
|
||||||
|
return this._configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
if (this.servicePromise) {
|
||||||
|
this.servicePromise
|
||||||
|
.then(childProcess => {
|
||||||
|
childProcess.kill()
|
||||||
|
})
|
||||||
|
.then(undefined, () => void 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
this.logger.dispose()
|
||||||
|
this._onTsServerStarted.dispose()
|
||||||
|
this._onResendModelsRequested.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
private info(message: string, data?: any): void {
|
||||||
|
this.logger.info(message, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
private error(message: string, data?: any): void {
|
||||||
|
this.logger.error(message, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public restartTsServer(): Promise<any> {
|
||||||
|
const start = () => {
|
||||||
|
this.servicePromise = this.startService(true)
|
||||||
|
return this.servicePromise
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.servicePromise) {
|
||||||
|
return Promise.resolve(this.servicePromise.then(childProcess => {
|
||||||
|
this.state = ServiceStat.Stopping
|
||||||
|
this.info('Killing TS Server')
|
||||||
|
childProcess.kill()
|
||||||
|
this.servicePromise = null
|
||||||
|
}).then(start))
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(start())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stop(): Promise<void> {
|
||||||
|
if (!this.servicePromise) return
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.servicePromise.then(childProcess => {
|
||||||
|
if (this.state == ServiceStat.Running) {
|
||||||
|
this.info('Killing TS Server')
|
||||||
|
childProcess.onExit(() => {
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
childProcess.kill()
|
||||||
|
this.servicePromise = null
|
||||||
|
} else {
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}, reject)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onTsServerStarted(): Event<API> {
|
||||||
|
return this._onTsServerStarted.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onProjectLanguageServiceStateChanged(): Event<
|
||||||
|
Proto.ProjectLanguageServiceStateEventBody
|
||||||
|
> {
|
||||||
|
return this._onProjectLanguageServiceStateChanged.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onDidBeginInstallTypings(): Event<Proto.BeginInstallTypesEventBody> {
|
||||||
|
return this._onDidBeginInstallTypings.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onDidEndInstallTypings(): Event<Proto.EndInstallTypesEventBody> {
|
||||||
|
return this._onDidEndInstallTypings.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onTypesInstallerInitializationFailed(): Event<Proto.TypesInstallerInitializationFailedEventBody> {
|
||||||
|
return this._onTypesInstallerInitializationFailed.event
|
||||||
|
}
|
||||||
|
|
||||||
|
public get apiVersion(): API {
|
||||||
|
return this._apiVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if (!this.servicePromise) {
|
||||||
|
this.startService().catch(err => {
|
||||||
|
workspace.showMessage(`TSServer start failed: ${err.message}`, 'error')
|
||||||
|
this.error(`Service start failed: ${err.stack}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startService(resendModels = false): Promise<ForkedTsServerProcess> {
|
||||||
|
let currentVersion = this.versionProvider.getLocalVersion(workspace.root)
|
||||||
|
if (!currentVersion || !fs.existsSync(currentVersion.tsServerPath)) {
|
||||||
|
currentVersion = await this.versionProvider.getDefaultVersion()
|
||||||
|
}
|
||||||
|
if (!currentVersion || !currentVersion.isValid) {
|
||||||
|
workspace.showMessage('Can not find tsserver, try installing...', 'error')
|
||||||
|
await workspace.terminal.installModule('typescript', 'tsserver')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
workspace.showMessage(`Using tsserver from: ${currentVersion.path}`) // tslint:disable-line
|
||||||
|
this._apiVersion = currentVersion.version
|
||||||
|
this.requestQueue = new RequestQueue()
|
||||||
|
this.callbacks = new CallbackMap()
|
||||||
|
this.lastError = null
|
||||||
|
const tsServerForkArgs = await this.getTsServerArgs()
|
||||||
|
const debugPort = this._configuration.debugPort
|
||||||
|
const options = {
|
||||||
|
execArgv: debugPort ? [`--inspect=${debugPort}`] : [], // [`--debug-brk=5859`]
|
||||||
|
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> {
|
||||||
|
this.state = ServiceStat.Starting
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
fork(
|
||||||
|
currentVersion.tsServerPath,
|
||||||
|
args,
|
||||||
|
options,
|
||||||
|
this.logger,
|
||||||
|
(err: any, childProcess: cp.ChildProcess | null) => {
|
||||||
|
if (err || !childProcess) {
|
||||||
|
this.state = ServiceStat.StartFailed
|
||||||
|
this.lastError = err
|
||||||
|
this.error('Starting TSServer failed with error.', err.stack)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.state = ServiceStat.Running
|
||||||
|
this.info('Started TSServer', JSON.stringify(currentVersion, null, 2))
|
||||||
|
const handle = new ForkedTsServerProcess(childProcess)
|
||||||
|
this.lastStart = Date.now()
|
||||||
|
|
||||||
|
handle.onError((err: Error) => {
|
||||||
|
this.lastError = err
|
||||||
|
this.error('TSServer errored with error.', err)
|
||||||
|
this.error(`TSServer log file: ${this.tsServerLogFile || ''}`)
|
||||||
|
workspace.showMessage(`TSServer errored with error. ${err.message}`, 'error')
|
||||||
|
this.serviceExited(false)
|
||||||
|
})
|
||||||
|
handle.onExit((code: any) => {
|
||||||
|
if (code == null) {
|
||||||
|
this.info('TSServer normal exit')
|
||||||
|
} else {
|
||||||
|
this.error(`TSServer exited with code: ${code}`)
|
||||||
|
}
|
||||||
|
this.info(`TSServer log file: ${this.tsServerLogFile || ''}`)
|
||||||
|
this.serviceExited(code != null)
|
||||||
|
})
|
||||||
|
|
||||||
|
handle.createReader(
|
||||||
|
msg => {
|
||||||
|
this.dispatchMessage(msg)
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.error('ReaderError', error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
resolve(handle)
|
||||||
|
this._onTsServerStarted.fire(currentVersion.version)
|
||||||
|
this.serviceStarted(resendModels)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
reject(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async openTsServerLogFile(): Promise<boolean> {
|
||||||
|
const isRoot = process.getuid && process.getuid() == 0
|
||||||
|
let echoErr = (msg: string) => {
|
||||||
|
workspace.showMessage(msg, 'error')
|
||||||
|
}
|
||||||
|
if (isRoot) {
|
||||||
|
echoErr('Log disabled for root user.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!this.apiVersion.gte(API.v222)) {
|
||||||
|
echoErr('TS Server logging requires TS 2.2.2+')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (this._configuration.tsServerLogLevel === TsServerLogLevel.Off) {
|
||||||
|
echoErr(`TS Server logging is off. Change 'tsserver.log' in 'coc-settings.json' to enable`)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (!this.tsServerLogFile) {
|
||||||
|
echoErr('TS Server has not started logging.')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await workspace.nvim.command(`edit ${this.tsServerLogFile}`)
|
||||||
|
return true
|
||||||
|
} catch {
|
||||||
|
echoErr('Could not open TS Server log file')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private serviceStarted(resendModels: boolean): void {
|
||||||
|
let document = workspace.getDocument(workspace.bufnr)
|
||||||
|
if (document) {
|
||||||
|
this.fileConfigurationManager.ensureConfigurationForDocument(document.textDocument) // tslint:disable-line
|
||||||
|
} else {
|
||||||
|
const configureOptions: Proto.ConfigureRequestArguments = {
|
||||||
|
hostInfo: 'nvim-coc'
|
||||||
|
}
|
||||||
|
this.execute('configure', configureOptions) // tslint:disable-line
|
||||||
|
}
|
||||||
|
this.setCompilerOptionsForInferredProjects(this._configuration)
|
||||||
|
if (resendModels) {
|
||||||
|
this._onResendModelsRequested.fire(void 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setCompilerOptionsForInferredProjects(
|
||||||
|
configuration: TypeScriptServiceConfiguration
|
||||||
|
): void {
|
||||||
|
if (!this.apiVersion.gte(API.v206)) return
|
||||||
|
const args: Proto.SetCompilerOptionsForInferredProjectsArgs = {
|
||||||
|
options: this.getCompilerOptionsForInferredProjects(configuration)
|
||||||
|
}
|
||||||
|
this.execute('compilerOptionsForInferredProjects', args, true) // tslint:disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCompilerOptionsForInferredProjects(
|
||||||
|
configuration: TypeScriptServiceConfiguration
|
||||||
|
): Proto.ExternalProjectCompilerOptions {
|
||||||
|
return {
|
||||||
|
...inferredProjectConfig(configuration),
|
||||||
|
allowJs: true,
|
||||||
|
allowSyntheticDefaultImports: true,
|
||||||
|
allowNonTsExtensions: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private serviceExited(restart: boolean): void {
|
||||||
|
this.state = ServiceStat.Stopped
|
||||||
|
this.servicePromise = null
|
||||||
|
this.tsServerLogFile = null
|
||||||
|
this.callbacks.destroy(new Error('Service died.'))
|
||||||
|
this.callbacks = new CallbackMap()
|
||||||
|
if (restart) {
|
||||||
|
const diff = Date.now() - this.lastStart
|
||||||
|
this.numberRestarts++
|
||||||
|
let startService = true
|
||||||
|
if (this.numberRestarts > 5) {
|
||||||
|
this.numberRestarts = 0
|
||||||
|
if (diff < 10 * 1000 /* 10 seconds */) {
|
||||||
|
this.lastStart = Date.now()
|
||||||
|
startService = false
|
||||||
|
workspace.showMessage('The TypeScript language service died 5 times right after it got started.', 'error') // tslint:disable-line
|
||||||
|
} else if (diff < 60 * 1000 /* 1 Minutes */) {
|
||||||
|
this.lastStart = Date.now()
|
||||||
|
workspace.showMessage('The TypeScript language service died unexpectedly 5 times in the last 5 Minutes.', 'error') // tslint:disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (startService) {
|
||||||
|
this.startService(true) // tslint:disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public toPath(uri: string): string {
|
||||||
|
return this.normalizePath(Uri.parse(uri))
|
||||||
|
}
|
||||||
|
|
||||||
|
public toResource(filepath: string): string {
|
||||||
|
if (this._apiVersion.gte(API.v213)) {
|
||||||
|
if (filepath.startsWith('untitled:')) {
|
||||||
|
let resource = Uri.parse(filepath)
|
||||||
|
if (this.inMemoryResourcePrefix) {
|
||||||
|
const dirName = path.dirname(resource.path)
|
||||||
|
const fileName = path.basename(resource.path)
|
||||||
|
if (fileName.startsWith(this.inMemoryResourcePrefix)) {
|
||||||
|
resource = resource.with({ path: path.posix.join(dirName, fileName.slice(this.inMemoryResourcePrefix.length)) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resource.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Uri.file(filepath).toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
public normalizePath(resource: Uri): string | null {
|
||||||
|
if (this._apiVersion.gte(API.v213)) {
|
||||||
|
if (resource.scheme !== 'file') {
|
||||||
|
const dirName = path.dirname(resource.path)
|
||||||
|
const fileName = this.inMemoryResourcePrefix + path.basename(resource.path)
|
||||||
|
return resource
|
||||||
|
.with({ path: path.posix.join(dirName, fileName) })
|
||||||
|
.toString(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = resource.fsPath
|
||||||
|
if (!result) return null
|
||||||
|
|
||||||
|
// Both \ and / must be escaped in regular expressions
|
||||||
|
return result.replace(new RegExp('\\' + this.pathSeparator, 'g'), '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
private get inMemoryResourcePrefix(): string {
|
||||||
|
return this._apiVersion.gte(API.v270) ? '^' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
public asUrl(filepath: string): Uri {
|
||||||
|
if (this._apiVersion.gte(API.v213)) {
|
||||||
|
if (filepath.startsWith('untitled:')) {
|
||||||
|
let resource = Uri.parse(filepath)
|
||||||
|
if (this.inMemoryResourcePrefix) {
|
||||||
|
const dirName = path.dirname(resource.path)
|
||||||
|
const fileName = path.basename(resource.path)
|
||||||
|
if (fileName.startsWith(this.inMemoryResourcePrefix)) {
|
||||||
|
resource = resource.with({
|
||||||
|
path: path.posix.join(
|
||||||
|
dirName,
|
||||||
|
fileName.slice(this.inMemoryResourcePrefix.length)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Uri.file(filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
public execute(
|
||||||
|
command: string,
|
||||||
|
args: any,
|
||||||
|
expectsResultOrToken?: boolean | CancellationToken
|
||||||
|
): Promise<any> {
|
||||||
|
if (this.servicePromise == null) {
|
||||||
|
return Promise.resolve()
|
||||||
|
}
|
||||||
|
let token: CancellationToken | undefined
|
||||||
|
let expectsResult = true
|
||||||
|
if (typeof expectsResultOrToken === 'boolean') {
|
||||||
|
expectsResult = expectsResultOrToken
|
||||||
|
} else {
|
||||||
|
token = expectsResultOrToken
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = this.requestQueue.createRequest(command, args)
|
||||||
|
const requestInfo: RequestItem = {
|
||||||
|
request,
|
||||||
|
callbacks: null
|
||||||
|
}
|
||||||
|
let result: Promise<any>
|
||||||
|
if (expectsResult) {
|
||||||
|
let wasCancelled = false
|
||||||
|
result = new Promise<any>((resolve, reject) => {
|
||||||
|
requestInfo.callbacks = { c: resolve, e: reject, start: Date.now() }
|
||||||
|
if (token) {
|
||||||
|
token.onCancellationRequested(() => {
|
||||||
|
wasCancelled = true
|
||||||
|
this.tryCancelRequest(request.seq)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).catch((err: any) => {
|
||||||
|
if (!wasCancelled && command != 'signatureHelp') {
|
||||||
|
this.error(`'${command}' request failed with error.`, err)
|
||||||
|
}
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
result = Promise.resolve(null)
|
||||||
|
}
|
||||||
|
this.requestQueue.push(requestInfo)
|
||||||
|
this.sendNextRequests()
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendNextRequests(): void {
|
||||||
|
while (
|
||||||
|
this.callbacks.pendingResponses === 0 &&
|
||||||
|
this.requestQueue.length > 0
|
||||||
|
) {
|
||||||
|
const item = this.requestQueue.shift()
|
||||||
|
if (item) {
|
||||||
|
this.sendRequest(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendRequest(requestItem: RequestItem): void {
|
||||||
|
const serverRequest = requestItem.request
|
||||||
|
this.tracer.traceRequest(
|
||||||
|
serverRequest,
|
||||||
|
!!requestItem.callbacks,
|
||||||
|
this.requestQueue.length
|
||||||
|
)
|
||||||
|
if (requestItem.callbacks) {
|
||||||
|
this.callbacks.add(serverRequest.seq, requestItem.callbacks)
|
||||||
|
}
|
||||||
|
this.service()
|
||||||
|
.then(childProcess => {
|
||||||
|
childProcess.write(serverRequest)
|
||||||
|
})
|
||||||
|
.then(undefined, err => {
|
||||||
|
const callback = this.callbacks.fetch(serverRequest.seq)
|
||||||
|
if (callback) {
|
||||||
|
callback.e(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private tryCancelRequest(seq: number): boolean {
|
||||||
|
try {
|
||||||
|
if (this.requestQueue.tryCancelPendingRequest(seq)) {
|
||||||
|
this.tracer.logTrace(`TypeScript Service: canceled request with sequence number ${seq}`)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v222) && this.cancellationPipeName) {
|
||||||
|
this.tracer.logTrace(`TypeScript Service: trying to cancel ongoing request with sequence number ${seq}`)
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(this.cancellationPipeName + seq, '')
|
||||||
|
} catch {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tracer.logTrace(
|
||||||
|
`TypeScript Service: tried to cancel request with sequence number ${seq}. But request got already delivered.`
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
const p = this.callbacks.fetch(seq)
|
||||||
|
if (p) {
|
||||||
|
p.e(new Error(`Cancelled Request ${seq}`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispatchMessage(message: Proto.Message): void {
|
||||||
|
try {
|
||||||
|
if (message.type === 'response') {
|
||||||
|
const response: Proto.Response = message as Proto.Response
|
||||||
|
const p = this.callbacks.fetch(response.request_seq)
|
||||||
|
if (p) {
|
||||||
|
this.tracer.traceResponse(response, p.start)
|
||||||
|
if (response.success) {
|
||||||
|
p.c(response)
|
||||||
|
} else {
|
||||||
|
p.e(response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (message.type === 'event') {
|
||||||
|
const event: Proto.Event = message as Proto.Event
|
||||||
|
this.tracer.traceEvent(event)
|
||||||
|
this.dispatchEvent(event)
|
||||||
|
} else {
|
||||||
|
throw new Error('Unknown message type ' + message.type + ' received')
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
this.sendNextRequests()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispatchEvent(event: Proto.Event): void {
|
||||||
|
switch (event.event) {
|
||||||
|
case 'syntaxDiag':
|
||||||
|
case 'semanticDiag':
|
||||||
|
case 'suggestionDiag':
|
||||||
|
const diagnosticEvent: Proto.DiagnosticEvent = event
|
||||||
|
if (diagnosticEvent.body && diagnosticEvent.body.diagnostics) {
|
||||||
|
this._onDiagnosticsReceived.fire({
|
||||||
|
kind: getDignosticsKind(event),
|
||||||
|
resource: this.asUrl(diagnosticEvent.body.file),
|
||||||
|
diagnostics: diagnosticEvent.body.diagnostics
|
||||||
|
})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'configFileDiag':
|
||||||
|
this._onConfigDiagnosticsReceived.fire(
|
||||||
|
event as Proto.ConfigFileDiagnosticEvent
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'projectLanguageServiceState':
|
||||||
|
if (event.body) {
|
||||||
|
this._onProjectLanguageServiceStateChanged.fire(
|
||||||
|
(event as Proto.ProjectLanguageServiceStateEvent).body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'beginInstallTypes':
|
||||||
|
if (event.body) {
|
||||||
|
this._onDidBeginInstallTypings.fire(
|
||||||
|
(event as Proto.BeginInstallTypesEvent).body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'endInstallTypes':
|
||||||
|
if (event.body) {
|
||||||
|
this._onDidEndInstallTypings.fire(
|
||||||
|
(event as Proto.EndInstallTypesEvent).body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'typesInstallerInitializationFailed':
|
||||||
|
if (event.body) {
|
||||||
|
this._onTypesInstallerInitializationFailed.fire(
|
||||||
|
(event as Proto.TypesInstallerInitializationFailedEvent).body
|
||||||
|
)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getTsServerArgs(): Promise<string[]> {
|
||||||
|
const args: string[] = []
|
||||||
|
args.push('--allowLocalPluginLoads')
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v250)) {
|
||||||
|
args.push('--useInferredProjectPerProjectRoot')
|
||||||
|
} else {
|
||||||
|
args.push('--useSingleInferredProject')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v206) && this._configuration.disableAutomaticTypeAcquisition) {
|
||||||
|
args.push('--disableAutomaticTypingAcquisition')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v222)) {
|
||||||
|
this.cancellationPipeName = getTempFile(`tscancellation-${makeRandomHexString(20)}`)
|
||||||
|
args.push('--cancellationPipeName', this.cancellationPipeName + '*')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v222)) {
|
||||||
|
const isRoot = process.getuid && process.getuid() == 0
|
||||||
|
if (this._configuration.tsServerLogLevel !== TsServerLogLevel.Off && !isRoot) {
|
||||||
|
const logDir = os.tmpdir()
|
||||||
|
if (logDir) {
|
||||||
|
this.tsServerLogFile = path.join(logDir, `coc-nvim-tsc.log`)
|
||||||
|
this.info('TSServer log file :', this.tsServerLogFile)
|
||||||
|
} else {
|
||||||
|
this.tsServerLogFile = null
|
||||||
|
this.error('Could not create TSServer log directory')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.tsServerLogFile) {
|
||||||
|
args.push(
|
||||||
|
'--logVerbosity',
|
||||||
|
TsServerLogLevel.toString(this._configuration.tsServerLogLevel)
|
||||||
|
)
|
||||||
|
args.push('--logFile', this.tsServerLogFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v230)) {
|
||||||
|
const plugins = this._configuration.tsServerPluginNames
|
||||||
|
const pluginRoot = this._configuration.tsServerPluginRoot
|
||||||
|
if (plugins.length) {
|
||||||
|
args.push('--globalPlugins', plugins.join(','))
|
||||||
|
if (pluginRoot) {
|
||||||
|
args.push('--pluginProbeLocations', pluginRoot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._configuration.typingsCacheLocation) {
|
||||||
|
args.push('--globalTypingsCacheLocation', `"${this._configuration.typingsCacheLocation}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v234)) {
|
||||||
|
if (this._configuration.npmLocation) {
|
||||||
|
args.push('--npmLocation', `"${this._configuration.npmLocation}"`)
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
args.push('--npmLocation', `"${which.sync('npm')}"`)
|
||||||
|
} catch (e) { } // tslint:disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.apiVersion.gte(API.v291)) {
|
||||||
|
args.push('--noGetErrOnBackgroundUpdate')
|
||||||
|
}
|
||||||
|
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDignosticsKind(event: Proto.Event): DiagnosticKind {
|
||||||
|
switch (event.event) {
|
||||||
|
case 'syntaxDiag':
|
||||||
|
return DiagnosticKind.Syntax
|
||||||
|
case 'semanticDiag':
|
||||||
|
return DiagnosticKind.Semantic
|
||||||
|
case 'suggestionDiag':
|
||||||
|
return DiagnosticKind.Suggestion
|
||||||
|
}
|
||||||
|
throw new Error('Unknown dignostics kind')
|
||||||
|
}
|
201
src/server/typescriptServiceClientHost.ts
Normal file
201
src/server/typescriptServiceClientHost.ts
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { DiagnosticKind, disposeAll, workspace } from 'coc.nvim'
|
||||||
|
import { Diagnostic, DiagnosticSeverity, Disposable } from 'vscode-languageserver-protocol'
|
||||||
|
import Uri from 'vscode-uri'
|
||||||
|
import LanguageProvider from './languageProvider'
|
||||||
|
import * as Proto from './protocol'
|
||||||
|
import * as PConst from './protocol.const'
|
||||||
|
import TypeScriptServiceClient from './typescriptServiceClient'
|
||||||
|
import { LanguageDescription } from './utils/languageDescription'
|
||||||
|
import * as typeConverters from './utils/typeConverters'
|
||||||
|
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'
|
||||||
|
|
||||||
|
// Style check diagnostics that can be reported as warnings
|
||||||
|
const styleCheckDiagnostics = [
|
||||||
|
6133, // variable is declared but never used
|
||||||
|
6138, // property is declared but its value is never read
|
||||||
|
7027, // unreachable code detected
|
||||||
|
7028, // unused label
|
||||||
|
7029, // fall through case in switch
|
||||||
|
7030 // not all code paths return a value
|
||||||
|
]
|
||||||
|
|
||||||
|
export default class TypeScriptServiceClientHost implements Disposable {
|
||||||
|
private readonly ataProgressReporter: AtaProgressReporter
|
||||||
|
private readonly typingsStatus: TypingsStatus
|
||||||
|
private readonly client: TypeScriptServiceClient
|
||||||
|
private readonly languages: LanguageProvider[] = []
|
||||||
|
private readonly languagePerId = new Map<string, LanguageProvider>()
|
||||||
|
private readonly disposables: Disposable[] = []
|
||||||
|
private reportStyleCheckAsWarnings = true
|
||||||
|
|
||||||
|
constructor(descriptions: LanguageDescription[]) {
|
||||||
|
const handleProjectChange = () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.triggerAllDiagnostics()
|
||||||
|
}, 1500)
|
||||||
|
}
|
||||||
|
|
||||||
|
const configFileWatcher = workspace.createFileSystemWatcher('**/[tj]sconfig.json')
|
||||||
|
this.disposables.push(configFileWatcher)
|
||||||
|
configFileWatcher.onDidCreate(
|
||||||
|
this.reloadProjects,
|
||||||
|
this,
|
||||||
|
this.disposables
|
||||||
|
)
|
||||||
|
configFileWatcher.onDidDelete(
|
||||||
|
this.reloadProjects,
|
||||||
|
this,
|
||||||
|
this.disposables
|
||||||
|
)
|
||||||
|
configFileWatcher.onDidChange(handleProjectChange, this, this.disposables)
|
||||||
|
|
||||||
|
this.client = new TypeScriptServiceClient()
|
||||||
|
this.disposables.push(this.client)
|
||||||
|
this.client.onDiagnosticsReceived(({ kind, resource, diagnostics }) => {
|
||||||
|
this.diagnosticsReceived(kind, resource, diagnostics)
|
||||||
|
}, null, this.disposables)
|
||||||
|
|
||||||
|
this.client.onConfigDiagnosticsReceived(diag => {
|
||||||
|
let { body } = diag
|
||||||
|
if (body) {
|
||||||
|
let { configFile, diagnostics } = body
|
||||||
|
if (diagnostics.length) {
|
||||||
|
workspace.showMessage(`Invalid config file: ${configFile}`, 'error')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, null, this.disposables)
|
||||||
|
|
||||||
|
this.typingsStatus = new TypingsStatus(this.client)
|
||||||
|
this.ataProgressReporter = new AtaProgressReporter(this.client)
|
||||||
|
for (const description of descriptions) { // tslint:disable-line
|
||||||
|
const manager = new LanguageProvider(
|
||||||
|
this.client,
|
||||||
|
description,
|
||||||
|
this.typingsStatus
|
||||||
|
)
|
||||||
|
this.languages.push(manager)
|
||||||
|
this.disposables.push(manager)
|
||||||
|
this.languagePerId.set(description.id, manager)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.ensureServiceStarted()
|
||||||
|
this.client.onTsServerStarted(() => {
|
||||||
|
this.triggerAllDiagnostics()
|
||||||
|
})
|
||||||
|
this.configurationChanged()
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
disposeAll(this.disposables)
|
||||||
|
this.typingsStatus.dispose()
|
||||||
|
this.ataProgressReporter.dispose()
|
||||||
|
}
|
||||||
|
|
||||||
|
public reset(): void {
|
||||||
|
for (let lang of this.languages) {
|
||||||
|
lang.fileConfigurationManager.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get serviceClient(): TypeScriptServiceClient {
|
||||||
|
return this.client
|
||||||
|
}
|
||||||
|
|
||||||
|
public reloadProjects(): void {
|
||||||
|
this.client.execute('reloadProjects', null, false) // tslint:disable-line
|
||||||
|
this.triggerAllDiagnostics()
|
||||||
|
}
|
||||||
|
|
||||||
|
// typescript or javascript
|
||||||
|
public getProvider(languageId: string): LanguageProvider {
|
||||||
|
return this.languagePerId.get(languageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
private configurationChanged(): void {
|
||||||
|
const config = workspace.getConfiguration('tsserver')
|
||||||
|
this.reportStyleCheckAsWarnings = config.get('reportStyleChecksAsWarnings', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
public findLanguage(resource: Uri): LanguageProvider | null {
|
||||||
|
try {
|
||||||
|
return this.languages.find(language => language.handles(resource))
|
||||||
|
} catch {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public handles(uri: string): boolean {
|
||||||
|
return this.findLanguage(Uri.parse(uri)) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private triggerAllDiagnostics(): void {
|
||||||
|
for (const language of this.languagePerId.values()) {
|
||||||
|
language.triggerAllDiagnostics()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private diagnosticsReceived(
|
||||||
|
kind: DiagnosticKind,
|
||||||
|
resource: Uri,
|
||||||
|
diagnostics: Proto.Diagnostic[]
|
||||||
|
): void {
|
||||||
|
const language = this.findLanguage(resource)
|
||||||
|
if (language) {
|
||||||
|
language.diagnosticsReceived(
|
||||||
|
kind,
|
||||||
|
resource,
|
||||||
|
this.createMarkerDatas(diagnostics))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createMarkerDatas(diagnostics: Proto.Diagnostic[]): Diagnostic[] {
|
||||||
|
return diagnostics.map(tsDiag => this.tsDiagnosticToLspDiagnostic(tsDiag))
|
||||||
|
}
|
||||||
|
|
||||||
|
private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): Diagnostic {
|
||||||
|
const { start, end, text } = diagnostic
|
||||||
|
const range = {
|
||||||
|
start: typeConverters.Position.fromLocation(start),
|
||||||
|
end: typeConverters.Position.fromLocation(end)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
range,
|
||||||
|
message: text,
|
||||||
|
code: diagnostic.code ? diagnostic.code : null,
|
||||||
|
severity: this.getDiagnosticSeverity(diagnostic),
|
||||||
|
source: diagnostic.source || 'tsserver',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDiagnosticSeverity(diagnostic: Proto.Diagnostic): DiagnosticSeverity {
|
||||||
|
if (
|
||||||
|
this.reportStyleCheckAsWarnings &&
|
||||||
|
this.isStyleCheckDiagnostic(diagnostic.code) &&
|
||||||
|
diagnostic.category === PConst.DiagnosticCategory.error
|
||||||
|
) {
|
||||||
|
return DiagnosticSeverity.Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (diagnostic.category) {
|
||||||
|
case PConst.DiagnosticCategory.error:
|
||||||
|
return DiagnosticSeverity.Error
|
||||||
|
|
||||||
|
case PConst.DiagnosticCategory.warning:
|
||||||
|
return DiagnosticSeverity.Warning
|
||||||
|
|
||||||
|
case PConst.DiagnosticCategory.suggestion:
|
||||||
|
return DiagnosticSeverity.Information
|
||||||
|
|
||||||
|
default:
|
||||||
|
return DiagnosticSeverity.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private isStyleCheckDiagnostic(code: number | undefined): boolean {
|
||||||
|
return code ? styleCheckDiagnostics.indexOf(code) !== -1 : false
|
||||||
|
}
|
||||||
|
}
|
53
src/server/utils/api.ts
Normal file
53
src/server/utils/api.ts
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import * as semver from 'semver'
|
||||||
|
|
||||||
|
export default class API {
|
||||||
|
private static fromSimpleString(value: string): API {
|
||||||
|
return new API(value, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly defaultVersion = API.fromSimpleString('1.0.0')
|
||||||
|
public static readonly v203 = API.fromSimpleString('2.0.3')
|
||||||
|
public static readonly v206 = API.fromSimpleString('2.0.6')
|
||||||
|
public static readonly v208 = API.fromSimpleString('2.0.8')
|
||||||
|
public static readonly v213 = API.fromSimpleString('2.1.3')
|
||||||
|
public static readonly v220 = API.fromSimpleString('2.2.0')
|
||||||
|
public static readonly v222 = API.fromSimpleString('2.2.2')
|
||||||
|
public static readonly v230 = API.fromSimpleString('2.3.0')
|
||||||
|
public static readonly v234 = API.fromSimpleString('2.3.4')
|
||||||
|
public static readonly v240 = API.fromSimpleString('2.4.0')
|
||||||
|
public static readonly v250 = API.fromSimpleString('2.5.0')
|
||||||
|
public static readonly v260 = API.fromSimpleString('2.6.0')
|
||||||
|
public static readonly v270 = API.fromSimpleString('2.7.0')
|
||||||
|
public static readonly v280 = API.fromSimpleString('2.8.0')
|
||||||
|
public static readonly v290 = API.fromSimpleString('2.9.0')
|
||||||
|
public static readonly v291 = API.fromSimpleString('2.9.1')
|
||||||
|
public static readonly v292 = API.fromSimpleString('2.9.2')
|
||||||
|
public static readonly v300 = API.fromSimpleString('3.0.0')
|
||||||
|
|
||||||
|
public static fromVersionString(versionString: string): API {
|
||||||
|
let version = semver.valid(versionString)
|
||||||
|
if (!version) {
|
||||||
|
return new API('invalid version', '1.0.0')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cut off any prerelease tag since we sometimes consume those on purpose.
|
||||||
|
const index = versionString.indexOf('-')
|
||||||
|
if (index >= 0) {
|
||||||
|
version = version.substr(0, index)
|
||||||
|
}
|
||||||
|
return new API(versionString, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
|
public readonly versionString: string,
|
||||||
|
private readonly version: string
|
||||||
|
) { }
|
||||||
|
|
||||||
|
public gte(other: API): boolean {
|
||||||
|
return semver.gte(this.version, other.version)
|
||||||
|
}
|
||||||
|
}
|
61
src/server/utils/async.ts
Normal file
61
src/server/utils/async.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
export type ITask<T> = () => T
|
||||||
|
|
||||||
|
export class Delayer<T> {
|
||||||
|
public defaultDelay: number
|
||||||
|
private timeout: any // Timer
|
||||||
|
private completionPromise: Promise<T | null> | null
|
||||||
|
private onSuccess: ((value?: T | Thenable<T>) => void) | null
|
||||||
|
private task: ITask<T> | null
|
||||||
|
|
||||||
|
constructor(defaultDelay: number) {
|
||||||
|
this.defaultDelay = defaultDelay
|
||||||
|
this.timeout = null
|
||||||
|
this.completionPromise = null
|
||||||
|
this.onSuccess = null
|
||||||
|
this.task = null
|
||||||
|
}
|
||||||
|
|
||||||
|
public trigger(
|
||||||
|
task: ITask<T>,
|
||||||
|
delay: number = this.defaultDelay
|
||||||
|
): Promise<T | null> {
|
||||||
|
this.task = task
|
||||||
|
if (delay >= 0) {
|
||||||
|
this.cancelTimeout()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.completionPromise) {
|
||||||
|
this.completionPromise = new Promise<T>(resolve => {
|
||||||
|
this.onSuccess = resolve
|
||||||
|
}).then(() => {
|
||||||
|
this.completionPromise = null
|
||||||
|
this.onSuccess = null
|
||||||
|
let result = this.task && this.task()
|
||||||
|
this.task = null
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delay >= 0 || this.timeout === null) {
|
||||||
|
this.timeout = setTimeout(() => {
|
||||||
|
this.timeout = null
|
||||||
|
if (this.onSuccess) {
|
||||||
|
this.onSuccess(undefined)
|
||||||
|
}
|
||||||
|
}, delay >= 0 ? delay : this.defaultDelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.completionPromise
|
||||||
|
}
|
||||||
|
|
||||||
|
private cancelTimeout(): void {
|
||||||
|
if (this.timeout !== null) {
|
||||||
|
clearTimeout(this.timeout)
|
||||||
|
this.timeout = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
47
src/server/utils/codeAction.ts
Normal file
47
src/server/utils/codeAction.ts
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { WorkspaceEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import * as typeConverters from './typeConverters'
|
||||||
|
|
||||||
|
export function getEditForCodeAction(
|
||||||
|
client: ITypeScriptServiceClient,
|
||||||
|
action: Proto.CodeAction
|
||||||
|
): WorkspaceEdit | undefined {
|
||||||
|
return action.changes && action.changes.length
|
||||||
|
? typeConverters.WorkspaceEdit.fromFileCodeEdits(client, action.changes)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applyCodeAction(
|
||||||
|
client: ITypeScriptServiceClient,
|
||||||
|
action: Proto.CodeAction
|
||||||
|
): Promise<boolean> {
|
||||||
|
const workspaceEdit = getEditForCodeAction(client, action)
|
||||||
|
if (workspaceEdit) {
|
||||||
|
if (!(await workspace.applyEdit(workspaceEdit))) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return applyCodeActionCommands(client, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applyCodeActionCommands(
|
||||||
|
client: ITypeScriptServiceClient,
|
||||||
|
action: Proto.CodeAction
|
||||||
|
): Promise<boolean> {
|
||||||
|
// make sure there is command
|
||||||
|
if (action.commands && action.commands.length) {
|
||||||
|
for (const command of action.commands) {
|
||||||
|
const response = await client.execute('applyCodeActionCommand', { command })
|
||||||
|
if (!response || !response.body) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
149
src/server/utils/completionItem.ts
Normal file
149
src/server/utils/completionItem.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'vscode-languageserver-protocol'
|
||||||
|
import { Document, workspace } from 'coc.nvim'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import * as PConst from '../protocol.const'
|
||||||
|
|
||||||
|
export function resolveItem(
|
||||||
|
item: CompletionItem,
|
||||||
|
document: Document,
|
||||||
|
): void {
|
||||||
|
let { textEdit, label } = item // tslint:disable-line
|
||||||
|
let { position } = item.data
|
||||||
|
if (textEdit) return
|
||||||
|
// try replace more characters after cursor
|
||||||
|
const wordRange = document.getWordRangeAtPosition(position)
|
||||||
|
let text = document.textDocument.getText({
|
||||||
|
start: {
|
||||||
|
line: position.line,
|
||||||
|
character: Math.max(0, position.character - label.length),
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: position.line,
|
||||||
|
character: position.character
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
text = text.toLowerCase()
|
||||||
|
const entryName = label.toLowerCase()
|
||||||
|
|
||||||
|
for (let i = entryName.length; i >= 0; --i) {
|
||||||
|
if (text.endsWith(entryName.substr(0, i)) &&
|
||||||
|
(!wordRange ||
|
||||||
|
wordRange.start.character > position.character - i)) {
|
||||||
|
item.textEdit = {
|
||||||
|
newText: label,
|
||||||
|
range: {
|
||||||
|
start: {
|
||||||
|
line: position.line,
|
||||||
|
character: Math.max(0, position.character - i)
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: position.line,
|
||||||
|
character: position.character
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertCompletionEntry(
|
||||||
|
tsEntry: Proto.CompletionEntry,
|
||||||
|
uri: string,
|
||||||
|
position: Position,
|
||||||
|
useCodeSnippetsOnMethodSuggest: boolean
|
||||||
|
): CompletionItem {
|
||||||
|
let label = tsEntry.name
|
||||||
|
let sortText = tsEntry.sortText
|
||||||
|
if (tsEntry.isRecommended) {
|
||||||
|
// Make sure isRecommended property always comes first
|
||||||
|
// https://github.com/Microsoft/vscode/issues/40325
|
||||||
|
sortText = '\0' + sortText
|
||||||
|
} else if (tsEntry.source) {
|
||||||
|
// De-prioritze auto-imports
|
||||||
|
// https://github.com/Microsoft/vscode/issues/40311
|
||||||
|
sortText = '\uffff' + sortText
|
||||||
|
} else {
|
||||||
|
sortText = tsEntry.sortText
|
||||||
|
}
|
||||||
|
let kind = convertKind(tsEntry.kind)
|
||||||
|
let insertTextFormat = (
|
||||||
|
useCodeSnippetsOnMethodSuggest &&
|
||||||
|
(kind === CompletionItemKind.Function ||
|
||||||
|
kind === CompletionItemKind.Method)
|
||||||
|
) ? InsertTextFormat.Snippet : InsertTextFormat.PlainText
|
||||||
|
|
||||||
|
let textEdit: TextEdit = null
|
||||||
|
let insertText = tsEntry.insertText
|
||||||
|
if (insertText) {
|
||||||
|
let document = workspace.getDocument(uri)
|
||||||
|
textEdit = {
|
||||||
|
range: document.getWordRangeAtPosition(position),
|
||||||
|
newText: insertText
|
||||||
|
}
|
||||||
|
insertText = null
|
||||||
|
}
|
||||||
|
let optional = tsEntry.kindModifiers && tsEntry.kindModifiers.match(/\boptional\b/)
|
||||||
|
return {
|
||||||
|
label,
|
||||||
|
insertText,
|
||||||
|
kind,
|
||||||
|
textEdit,
|
||||||
|
insertTextFormat,
|
||||||
|
sortText,
|
||||||
|
data: {
|
||||||
|
uri,
|
||||||
|
optional,
|
||||||
|
position,
|
||||||
|
source: tsEntry.source || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function convertKind(kind: string): CompletionItemKind {
|
||||||
|
switch (kind) {
|
||||||
|
case PConst.Kind.primitiveType:
|
||||||
|
case PConst.Kind.keyword:
|
||||||
|
return CompletionItemKind.Keyword
|
||||||
|
case PConst.Kind.const:
|
||||||
|
return CompletionItemKind.Constant
|
||||||
|
case PConst.Kind.let:
|
||||||
|
case PConst.Kind.variable:
|
||||||
|
case PConst.Kind.localVariable:
|
||||||
|
case PConst.Kind.alias:
|
||||||
|
return CompletionItemKind.Variable
|
||||||
|
case PConst.Kind.memberVariable:
|
||||||
|
case PConst.Kind.memberGetAccessor:
|
||||||
|
case PConst.Kind.memberSetAccessor:
|
||||||
|
return CompletionItemKind.Field
|
||||||
|
case PConst.Kind.function:
|
||||||
|
return CompletionItemKind.Function
|
||||||
|
case PConst.Kind.memberFunction:
|
||||||
|
case PConst.Kind.constructSignature:
|
||||||
|
case PConst.Kind.callSignature:
|
||||||
|
case PConst.Kind.indexSignature:
|
||||||
|
return CompletionItemKind.Method
|
||||||
|
case PConst.Kind.enum:
|
||||||
|
return CompletionItemKind.Enum
|
||||||
|
case PConst.Kind.module:
|
||||||
|
case PConst.Kind.externalModuleName:
|
||||||
|
return CompletionItemKind.Module
|
||||||
|
case PConst.Kind.class:
|
||||||
|
case PConst.Kind.type:
|
||||||
|
return CompletionItemKind.Class
|
||||||
|
case PConst.Kind.interface:
|
||||||
|
return CompletionItemKind.Interface
|
||||||
|
case PConst.Kind.warning:
|
||||||
|
case PConst.Kind.file:
|
||||||
|
case PConst.Kind.script:
|
||||||
|
return CompletionItemKind.File
|
||||||
|
case PConst.Kind.directory:
|
||||||
|
return CompletionItemKind.Folder
|
||||||
|
}
|
||||||
|
return CompletionItemKind.Property
|
||||||
|
}
|
109
src/server/utils/configuration.ts
Normal file
109
src/server/utils/configuration.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import { workspace, WorkspaceConfiguration } from 'coc.nvim'
|
||||||
|
import which from 'which'
|
||||||
|
|
||||||
|
export enum TsServerLogLevel {
|
||||||
|
Off,
|
||||||
|
Normal,
|
||||||
|
Terse,
|
||||||
|
Verbose
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace TsServerLogLevel {
|
||||||
|
export function fromString(value: string): TsServerLogLevel {
|
||||||
|
switch (value && value.toLowerCase()) {
|
||||||
|
case 'normal':
|
||||||
|
return TsServerLogLevel.Normal
|
||||||
|
case 'terse':
|
||||||
|
return TsServerLogLevel.Terse
|
||||||
|
case 'verbose':
|
||||||
|
return TsServerLogLevel.Verbose
|
||||||
|
case 'off':
|
||||||
|
default:
|
||||||
|
return TsServerLogLevel.Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toString(value: TsServerLogLevel): string {
|
||||||
|
switch (value) {
|
||||||
|
case TsServerLogLevel.Normal:
|
||||||
|
return 'normal'
|
||||||
|
case TsServerLogLevel.Terse:
|
||||||
|
return 'terse'
|
||||||
|
case TsServerLogLevel.Verbose:
|
||||||
|
return 'verbose'
|
||||||
|
case TsServerLogLevel.Off:
|
||||||
|
default:
|
||||||
|
return 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeScriptServiceConfiguration {
|
||||||
|
private _configuration: WorkspaceConfiguration
|
||||||
|
private constructor() {
|
||||||
|
this._configuration = workspace.getConfiguration('tsserver')
|
||||||
|
|
||||||
|
workspace.onDidChangeConfiguration(() => {
|
||||||
|
this._configuration = workspace.getConfiguration('tsserver')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public get locale(): string | null {
|
||||||
|
return this._configuration.get<string | null>('locale', null)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get globalTsdk(): string | null {
|
||||||
|
return this._configuration.get<string | null>('tsdk', null)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tsServerLogLevel(): TsServerLogLevel {
|
||||||
|
return TsServerLogLevel.fromString(this._configuration.get<string | null>('log', null))
|
||||||
|
}
|
||||||
|
|
||||||
|
public get typingsCacheLocation(): string {
|
||||||
|
return this._configuration.get<string>('typingsCacheLocation', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tsServerPluginNames(): string[] {
|
||||||
|
return this._configuration.get<string[]>('pluginNames', [])
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tsServerPluginRoot(): string | null {
|
||||||
|
return this._configuration.get<string | null>('tsServerPluginRoot', null)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get checkJs(): boolean {
|
||||||
|
return this._configuration.get<boolean>('implicitProjectConfig.checkJs', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get experimentalDecorators(): boolean {
|
||||||
|
return this._configuration.get<boolean>('implicitProjectConfig.experimentalDecorators', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get disableAutomaticTypeAcquisition(): boolean {
|
||||||
|
return this._configuration.get<boolean>('disableAutomaticTypeAcquisition', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get formatOnType(): boolean {
|
||||||
|
return this._configuration.get<boolean>('formatOnType', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
public get debugPort(): number | null {
|
||||||
|
return this._configuration.get<number>('debugPort', parseInt(process.env['TSS_DEBUG'], 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
public get npmLocation(): string | null {
|
||||||
|
let path = this._configuration.get<string>('npm', '')
|
||||||
|
if (path) return path
|
||||||
|
try {
|
||||||
|
path = which.sync('npm')
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
public static loadFromWorkspace(): TypeScriptServiceConfiguration {
|
||||||
|
return new TypeScriptServiceConfiguration()
|
||||||
|
}
|
||||||
|
}
|
30
src/server/utils/fs.ts
Normal file
30
src/server/utils/fs.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import path from 'path'
|
||||||
|
import os from 'os'
|
||||||
|
import fs from 'fs'
|
||||||
|
|
||||||
|
export function getParentDirs(fullpath: string): string[] {
|
||||||
|
let obj = path.parse(fullpath)
|
||||||
|
if (!obj || !obj.root) return []
|
||||||
|
let res = []
|
||||||
|
let p = path.dirname(fullpath)
|
||||||
|
while (p && p !== obj.root) {
|
||||||
|
res.push(p)
|
||||||
|
p = path.dirname(p)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveRoot(cwd: string, subs: string[], home?: string): string | null {
|
||||||
|
home = home || os.homedir()
|
||||||
|
let { root } = path.parse(cwd)
|
||||||
|
let paths = getParentDirs(cwd)
|
||||||
|
paths.unshift(cwd)
|
||||||
|
for (let p of paths) {
|
||||||
|
if (p == home || p == root) return null
|
||||||
|
for (let sub of subs) {
|
||||||
|
let d = path.join(p, sub)
|
||||||
|
if (fs.existsSync(d)) return path.dirname(d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return root
|
||||||
|
}
|
18
src/server/utils/is.ts
Normal file
18
src/server/utils/is.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
const toString = Object.prototype.toString
|
||||||
|
|
||||||
|
export function defined(value: any): boolean {
|
||||||
|
return typeof value !== 'undefined'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function boolean(value: any): value is boolean {
|
||||||
|
return value === true || value === false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function string(value: any): value is string {
|
||||||
|
return toString.call(value) === '[object String]'
|
||||||
|
}
|
32
src/server/utils/languageDescription.ts
Normal file
32
src/server/utils/languageDescription.ts
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import * as languageModeIds from './languageModeIds'
|
||||||
|
|
||||||
|
export interface LanguageDescription {
|
||||||
|
readonly id: string
|
||||||
|
readonly diagnosticSource: string
|
||||||
|
readonly modeIds: string[]
|
||||||
|
readonly configFile?: string
|
||||||
|
readonly isExternal?: boolean
|
||||||
|
readonly diagnosticOwner: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const standardLanguageDescriptions: LanguageDescription[] = [
|
||||||
|
{
|
||||||
|
id: 'typescript',
|
||||||
|
diagnosticSource: 'ts',
|
||||||
|
diagnosticOwner: 'typescript',
|
||||||
|
modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact,
|
||||||
|
languageModeIds.typescripttsx, languageModeIds.typescriptjsx],
|
||||||
|
configFile: 'tsconfig.json'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'javascript',
|
||||||
|
diagnosticSource: 'ts',
|
||||||
|
diagnosticOwner: 'typescript',
|
||||||
|
modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact],
|
||||||
|
configFile: 'jsconfig.json'
|
||||||
|
}
|
||||||
|
]
|
14
src/server/utils/languageModeIds.ts
Normal file
14
src/server/utils/languageModeIds.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
export const typescript = 'typescript'
|
||||||
|
export const typescriptreact = 'typescriptreact'
|
||||||
|
export const typescripttsx = 'typescript.tsx'
|
||||||
|
export const typescriptjsx = 'typescript.jsx'
|
||||||
|
export const javascript = 'javascript'
|
||||||
|
export const javascriptreact = 'javascript.jsx'
|
||||||
|
export const jsxTags = 'jsx-tags'
|
||||||
|
|
||||||
|
export const languageIds = [typescript, typescriptreact, javascript, javascriptreact, typescripttsx, jsxTags]
|
67
src/server/utils/logger.ts
Normal file
67
src/server/utils/logger.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { OutputChannel, workspace } from 'coc.nvim'
|
||||||
|
import * as is from './is'
|
||||||
|
|
||||||
|
export default class Logger {
|
||||||
|
|
||||||
|
private _channel: OutputChannel
|
||||||
|
|
||||||
|
private get output(): OutputChannel {
|
||||||
|
if (this._channel) {
|
||||||
|
return this._channel
|
||||||
|
}
|
||||||
|
this._channel = workspace.createOutputChannel('tsserver')
|
||||||
|
return this._channel
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
if (this._channel) {
|
||||||
|
this._channel.dispose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data2String(data: any): string {
|
||||||
|
if (data instanceof Error) {
|
||||||
|
if (is.string(data.stack)) {
|
||||||
|
return data.stack
|
||||||
|
}
|
||||||
|
return (data as Error).message
|
||||||
|
}
|
||||||
|
if (is.boolean(data.success) && !data.success && is.string(data.message)) {
|
||||||
|
return data.message
|
||||||
|
}
|
||||||
|
if (is.string(data)) {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
return data.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
public info(message: string, data?: any): void {
|
||||||
|
this.logLevel('Info', message, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public warn(message: string, data?: any): void {
|
||||||
|
this.logLevel('Warn', message, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public error(message: string, data?: any): void {
|
||||||
|
// See https://github.com/Microsoft/TypeScript/issues/10496
|
||||||
|
if (data && data.message === 'No content available.') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.logLevel('Error', message, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public logLevel(level: string, message: string, data?: any): void {
|
||||||
|
this.output.appendLine(
|
||||||
|
`[${level} - ${new Date().toLocaleTimeString()}] ${message}`
|
||||||
|
)
|
||||||
|
if (data) {
|
||||||
|
this.output.appendLine(this.data2String(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
src/server/utils/previewer.ts
Normal file
73
src/server/utils/previewer.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { MarkupContent, MarkupKind } from 'vscode-languageserver-protocol'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
|
||||||
|
function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
|
||||||
|
if (!tag.text) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (tag.name) {
|
||||||
|
case 'example':
|
||||||
|
case 'default':
|
||||||
|
// Convert to markdown code block if it not already one
|
||||||
|
if (tag.text.match(/^\s*[~`]{3}/g)) {
|
||||||
|
return tag.text
|
||||||
|
}
|
||||||
|
return '```\n' + tag.text + '\n```'
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag.text
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
|
||||||
|
switch (tag.name) {
|
||||||
|
case 'param':
|
||||||
|
const body = (tag.text || '').split(/^([\w\.]+)\s*/)
|
||||||
|
if (body && body.length === 3) {
|
||||||
|
const param = body[1]
|
||||||
|
const doc = body[2]
|
||||||
|
const label = `*@${tag.name}* \`${param}\``
|
||||||
|
if (!doc) {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return label + (doc.match(/\r\n|\n/g) ? '\n' + doc : ` — ${doc}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic tag
|
||||||
|
const label = `*@${tag.name}*`
|
||||||
|
const text = getTagBodyText(tag)
|
||||||
|
if (!text) {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
return label + (text.match(/\r\n|\n/g) ? '\n' + text : ` — ${text}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function plain(parts: Proto.SymbolDisplayPart[]): string {
|
||||||
|
if (!parts || !parts.length) return ''
|
||||||
|
return parts.map(part => part.text).join('')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function tagsMarkdownPreview(tags: Proto.JSDocTagInfo[]): string {
|
||||||
|
return (tags || []).map(getTagDocumentation).join(' \n\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function markdownDocumentation(
|
||||||
|
documentation: Proto.SymbolDisplayPart[],
|
||||||
|
tags: Proto.JSDocTagInfo[]
|
||||||
|
): MarkupContent {
|
||||||
|
let out = plain(documentation)
|
||||||
|
const tagsPreview = tagsMarkdownPreview(tags)
|
||||||
|
if (tagsPreview) {
|
||||||
|
out = out + ('\n\n' + tagsPreview)
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
kind: MarkupKind.Markdown,
|
||||||
|
value: out
|
||||||
|
}
|
||||||
|
}
|
154
src/server/utils/process.ts
Normal file
154
src/server/utils/process.ts
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 net from 'net'
|
||||||
|
import os from 'os'
|
||||||
|
import path from 'path'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import Logger from './logger'
|
||||||
|
|
||||||
|
export interface IForkOptions {
|
||||||
|
cwd?: string
|
||||||
|
execArgv?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeRandomHexString(length: number): string {
|
||||||
|
let chars = ['0', '1', '2', '3', '4', '5', '6', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
|
||||||
|
let result = ''
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const idx = Math.floor(chars.length * Math.random())
|
||||||
|
result += chars[idx]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePipeName(): string {
|
||||||
|
return getPipeName(makeRandomHexString(40))
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPipeName(name: string): string {
|
||||||
|
const fullName = 'coc-tsc-' + name
|
||||||
|
if (process.platform === 'win32') {
|
||||||
|
return '\\\\.\\pipe\\' + fullName + '-sock'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mac/Unix: use socket file
|
||||||
|
return path.join(os.tmpdir(), fullName + '.sock')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTempFile(name: string): string {
|
||||||
|
const fullName = 'coc-nvim-' + name
|
||||||
|
return path.join(os.tmpdir(), fullName + '.sock')
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePatchedEnv(
|
||||||
|
env: any,
|
||||||
|
stdInPipeName: string,
|
||||||
|
stdOutPipeName: string,
|
||||||
|
stdErrPipeName: string
|
||||||
|
): any {
|
||||||
|
const newEnv = Object.assign({}, env)
|
||||||
|
|
||||||
|
// Set the two unique pipe names and the electron flag as process env
|
||||||
|
newEnv['STDIN_PIPE_NAME'] = stdInPipeName // tslint:disable-line
|
||||||
|
newEnv['STDOUT_PIPE_NAME'] = stdOutPipeName // tslint:disable-line
|
||||||
|
newEnv['STDERR_PIPE_NAME'] = stdErrPipeName // tslint:disable-line
|
||||||
|
newEnv['TSS_LOG'] = `-level verbose -file ${path.join(os.tmpdir(), 'coc-nvim-tsc.log')}` // tslint:disable-line
|
||||||
|
|
||||||
|
// Ensure we always have a PATH set
|
||||||
|
newEnv['PATH'] = newEnv['PATH'] || process.env.PATH // tslint:disable-line
|
||||||
|
return newEnv
|
||||||
|
}
|
||||||
|
|
||||||
|
export function fork(
|
||||||
|
modulePath: string,
|
||||||
|
args: string[],
|
||||||
|
options: IForkOptions,
|
||||||
|
logger: Logger,
|
||||||
|
callback: (error: any, cp: cp.ChildProcess | null) => void
|
||||||
|
): void {
|
||||||
|
let callbackCalled = false
|
||||||
|
const resolve = (result: cp.ChildProcess) => {
|
||||||
|
if (callbackCalled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callbackCalled = true
|
||||||
|
callback(null, result)
|
||||||
|
}
|
||||||
|
const reject = (err: any) => {
|
||||||
|
if (callbackCalled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
callbackCalled = true
|
||||||
|
callback(err, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate three unique pipe names
|
||||||
|
const stdInPipeName = generatePipeName()
|
||||||
|
const stdOutPipeName = generatePipeName()
|
||||||
|
const stdErrPipeName = generatePipeName()
|
||||||
|
|
||||||
|
const newEnv = generatePatchedEnv(
|
||||||
|
process.env,
|
||||||
|
stdInPipeName,
|
||||||
|
stdOutPipeName,
|
||||||
|
stdErrPipeName
|
||||||
|
)
|
||||||
|
newEnv['NODE_PATH'] = path.join(modulePath, '..', '..', '..') // 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
|
||||||
|
logger.info('Forking TSServer', `PATH: ${newEnv['PATH']} `)
|
||||||
|
|
||||||
|
const bootstrapperPath = path.join(workspace.pluginRoot, 'bin/tsserverForkStart')
|
||||||
|
childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), {
|
||||||
|
silent: true,
|
||||||
|
env: newEnv,
|
||||||
|
execArgv: options.execArgv
|
||||||
|
})
|
||||||
|
|
||||||
|
childProcess.once('error', (err: Error) => {
|
||||||
|
closeServer()
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
childProcess.once('exit', (err: Error) => {
|
||||||
|
closeServer()
|
||||||
|
reject(err)
|
||||||
|
})
|
||||||
|
}
|
8
src/server/utils/regexp.ts
Normal file
8
src/server/utils/regexp.ts
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
export function escapeRegExp(text: string): string {
|
||||||
|
return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&')
|
||||||
|
}
|
101
src/server/utils/tracer.ts
Normal file
101
src/server/utils/tracer.ts
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import Logger from './logger'
|
||||||
|
|
||||||
|
enum Trace {
|
||||||
|
Off,
|
||||||
|
Messages,
|
||||||
|
Verbose
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Trace {
|
||||||
|
export function fromString(value: string): Trace {
|
||||||
|
value = value || ''
|
||||||
|
value = value.toLowerCase()
|
||||||
|
switch (value) {
|
||||||
|
case 'off':
|
||||||
|
return Trace.Off
|
||||||
|
case 'messages':
|
||||||
|
return Trace.Messages
|
||||||
|
case 'verbose':
|
||||||
|
return Trace.Verbose
|
||||||
|
default:
|
||||||
|
return Trace.Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Tracer {
|
||||||
|
private trace?: Trace
|
||||||
|
|
||||||
|
constructor(private readonly logger: Logger) {
|
||||||
|
this.trace = Tracer.readTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readTrace(): Trace {
|
||||||
|
let result: Trace = Trace.fromString(workspace.getConfiguration('tsserver').get<string>('trace.server', 'off'))
|
||||||
|
if (result === Trace.Off && !!process.env.TSS_TRACE) {
|
||||||
|
result = Trace.Messages
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public traceRequest(
|
||||||
|
request: Proto.Request,
|
||||||
|
responseExpected: boolean,
|
||||||
|
queueLength: number
|
||||||
|
): void {
|
||||||
|
if (this.trace === Trace.Off) return
|
||||||
|
let data: string | undefined
|
||||||
|
if (this.trace === Trace.Verbose && request.arguments) {
|
||||||
|
data = `Arguments: ${JSON.stringify(request.arguments, null, 4)}`
|
||||||
|
}
|
||||||
|
this.logTrace(
|
||||||
|
`Sending request: ${request.command} (${
|
||||||
|
request.seq
|
||||||
|
}). Response expected: ${
|
||||||
|
responseExpected ? 'yes' : 'no'
|
||||||
|
}. Current queue length: ${queueLength}`,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public traceResponse(response: Proto.Response, startTime: number): void {
|
||||||
|
if (this.trace === Trace.Off) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let data: string | undefined
|
||||||
|
if (this.trace === Trace.Verbose && response.body) {
|
||||||
|
data = `Result: ${JSON.stringify(response.body, null, 4)}`
|
||||||
|
}
|
||||||
|
this.logTrace(
|
||||||
|
`Response received: ${response.command} (${
|
||||||
|
response.request_seq
|
||||||
|
}). Request took ${Date.now() - startTime} ms. Success: ${
|
||||||
|
response.success
|
||||||
|
} ${!response.success ? '. Message: ' + response.message : ''}`,
|
||||||
|
data
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public traceEvent(event: Proto.Event): void {
|
||||||
|
if (this.trace === Trace.Off) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let data: string | undefined
|
||||||
|
if (this.trace === Trace.Verbose && event.body) {
|
||||||
|
data = `Data: ${JSON.stringify(event.body, null, 4)}`
|
||||||
|
}
|
||||||
|
this.logTrace(`Event received: ${event.event} (${event.seq}).`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public logTrace(message: string, data?: any): void {
|
||||||
|
if (this.trace !== Trace.Off) {
|
||||||
|
this.logger.logLevel('Trace', message, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
src/server/utils/tsconfig.ts
Normal file
26
src/server/utils/tsconfig.ts
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import * as Proto from '../protocol'
|
||||||
|
import { TypeScriptServiceConfiguration } from './configuration'
|
||||||
|
|
||||||
|
export function inferredProjectConfig(
|
||||||
|
config: TypeScriptServiceConfiguration
|
||||||
|
): Proto.ExternalProjectCompilerOptions {
|
||||||
|
const base: Proto.ExternalProjectCompilerOptions = {
|
||||||
|
module: 'commonjs' as Proto.ModuleKind,
|
||||||
|
target: 'es2016' as Proto.ScriptTarget,
|
||||||
|
jsx: 'preserve' as Proto.JsxEmit
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.checkJs) {
|
||||||
|
base.checkJs = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.experimentalDecorators) {
|
||||||
|
base.experimentalDecorators = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return base
|
||||||
|
}
|
91
src/server/utils/typeConverters.ts
Normal file
91
src/server/utils/typeConverters.ts
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
/**
|
||||||
|
* Helpers for converting FROM LanguageServer types language-server ts types
|
||||||
|
*/
|
||||||
|
import * as language from 'vscode-languageserver-protocol'
|
||||||
|
import Proto from '../protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
|
||||||
|
export namespace Range {
|
||||||
|
export const fromTextSpan = (span: Proto.TextSpan): language.Range => {
|
||||||
|
return {
|
||||||
|
start: {
|
||||||
|
line: span.start.line - 1,
|
||||||
|
character: span.start.offset - 1
|
||||||
|
},
|
||||||
|
end: {
|
||||||
|
line: span.end.line - 1,
|
||||||
|
character: span.end.offset - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toFileRangeRequestArgs = (
|
||||||
|
file: string,
|
||||||
|
range: language.Range
|
||||||
|
): Proto.FileRangeRequestArgs => ({
|
||||||
|
file,
|
||||||
|
startLine: range.start.line + 1,
|
||||||
|
startOffset: range.start.character + 1,
|
||||||
|
endLine: range.end.line + 1,
|
||||||
|
endOffset: range.end.character + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Position {
|
||||||
|
export const fromLocation = (tslocation: Proto.Location): language.Position => {
|
||||||
|
return {
|
||||||
|
line: tslocation.line - 1,
|
||||||
|
character: tslocation.offset - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toFileLocationRequestArgs = (
|
||||||
|
file: string,
|
||||||
|
position: language.Position
|
||||||
|
): Proto.FileLocationRequestArgs => ({
|
||||||
|
file,
|
||||||
|
line: position.line + 1,
|
||||||
|
offset: position.character + 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace Location {
|
||||||
|
export const fromTextSpan = (
|
||||||
|
uri: string,
|
||||||
|
tsTextSpan: Proto.TextSpan
|
||||||
|
): language.Location => {
|
||||||
|
return {
|
||||||
|
uri,
|
||||||
|
range: Range.fromTextSpan(tsTextSpan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace TextEdit {
|
||||||
|
export const fromCodeEdit = (edit: Proto.CodeEdit): language.TextEdit => {
|
||||||
|
return {
|
||||||
|
range: Range.fromTextSpan(edit),
|
||||||
|
newText: edit.newText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace WorkspaceEdit {
|
||||||
|
export function fromFileCodeEdits(
|
||||||
|
client: ITypeScriptServiceClient,
|
||||||
|
edits: Iterable<Proto.FileCodeEdits>
|
||||||
|
): language.WorkspaceEdit {
|
||||||
|
let changes = {}
|
||||||
|
for (const edit of edits) {
|
||||||
|
let uri = client.toResource(edit.fileName)
|
||||||
|
changes[uri] = edit.textChanges.map(change => {
|
||||||
|
return TextEdit.fromCodeEdit(change)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return { changes }
|
||||||
|
}
|
||||||
|
}
|
115
src/server/utils/typingsStatus.ts
Normal file
115
src/server/utils/typingsStatus.ts
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import { Disposable } from 'vscode-languageserver-protocol'
|
||||||
|
import { ITypeScriptServiceClient } from '../typescriptService'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
|
||||||
|
const typingsInstallTimeout = 30 * 1000
|
||||||
|
|
||||||
|
export default class TypingsStatus implements Disposable {
|
||||||
|
private _acquiringTypings: { [eventId: string]: NodeJS.Timer } = Object.create(
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
private _client: ITypeScriptServiceClient
|
||||||
|
private _subscriptions: Disposable[] = []
|
||||||
|
|
||||||
|
constructor(client: ITypeScriptServiceClient) {
|
||||||
|
this._client = client
|
||||||
|
this._subscriptions.push(
|
||||||
|
this._client.onDidBeginInstallTypings(event =>
|
||||||
|
this.onBeginInstallTypings(event.eventId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
this._subscriptions.push(
|
||||||
|
this._client.onDidEndInstallTypings(event =>
|
||||||
|
this.onEndInstallTypings(event.eventId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this._subscriptions.forEach(x => x.dispose())
|
||||||
|
|
||||||
|
for (const eventId of Object.keys(this._acquiringTypings)) {
|
||||||
|
clearTimeout(this._acquiringTypings[eventId])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isAcquiringTypings(): boolean {
|
||||||
|
return Object.keys(this._acquiringTypings).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private onBeginInstallTypings(eventId: number): void {
|
||||||
|
if (this._acquiringTypings[eventId]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._acquiringTypings[eventId] = setTimeout(() => {
|
||||||
|
this.onEndInstallTypings(eventId)
|
||||||
|
}, typingsInstallTimeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
private onEndInstallTypings(eventId: number): void {
|
||||||
|
const timer = this._acquiringTypings[eventId]
|
||||||
|
if (timer) {
|
||||||
|
clearTimeout(timer)
|
||||||
|
}
|
||||||
|
delete this._acquiringTypings[eventId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AtaProgressReporter {
|
||||||
|
private _promises = new Map<number, Function>()
|
||||||
|
private _disposable: Disposable
|
||||||
|
private _invalid = false
|
||||||
|
|
||||||
|
constructor(client: ITypeScriptServiceClient) {
|
||||||
|
const disposables: Disposable[] = []
|
||||||
|
disposables.push(client.onDidBeginInstallTypings(e => this._onBegin(e.eventId)))
|
||||||
|
disposables.push(client.onDidEndInstallTypings(e => this._onEndOrTimeout(e.eventId)))
|
||||||
|
disposables.push(client.onTypesInstallerInitializationFailed(_ =>
|
||||||
|
this.onTypesInstallerInitializationFailed()
|
||||||
|
))
|
||||||
|
this._disposable = Disposable.create(() => {
|
||||||
|
disposables.forEach(disposable => {
|
||||||
|
disposable.dispose()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
this._disposable.dispose()
|
||||||
|
this._promises.forEach(value => value())
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onBegin(eventId: number): void {
|
||||||
|
const handle = setTimeout(
|
||||||
|
() => this._onEndOrTimeout(eventId),
|
||||||
|
typingsInstallTimeout
|
||||||
|
)
|
||||||
|
new Promise(resolve => { // tslint:disable-line
|
||||||
|
this._promises.set(eventId, () => {
|
||||||
|
clearTimeout(handle)
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
workspace.showMessage('Fetching data for better TypeScript IntelliSense')
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onEndOrTimeout(eventId: number): void {
|
||||||
|
const resolve = this._promises.get(eventId)
|
||||||
|
if (resolve) {
|
||||||
|
this._promises.delete(eventId)
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onTypesInstallerInitializationFailed() { // tslint:disable-line
|
||||||
|
if (!this._invalid) {
|
||||||
|
workspace.showMessage('Could not install typings files for JavaScript language features. Please ensure that NPM is installed', 'error')
|
||||||
|
}
|
||||||
|
this._invalid = true
|
||||||
|
}
|
||||||
|
}
|
137
src/server/utils/versionProvider.ts
Normal file
137
src/server/utils/versionProvider.ts
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import fs from 'fs'
|
||||||
|
import path from 'path'
|
||||||
|
import { getParentDirs } from './fs'
|
||||||
|
import { workspace } from 'coc.nvim'
|
||||||
|
import API from './api'
|
||||||
|
import { TypeScriptServiceConfiguration } from './configuration'
|
||||||
|
|
||||||
|
export class TypeScriptVersion {
|
||||||
|
private _api: API | null | undefined
|
||||||
|
constructor(
|
||||||
|
public readonly path: string,
|
||||||
|
private readonly _pathLabel?: string
|
||||||
|
) {
|
||||||
|
this._api = null
|
||||||
|
}
|
||||||
|
|
||||||
|
public get tsServerPath(): string {
|
||||||
|
return path.join(this.path, 'tsserver.js')
|
||||||
|
}
|
||||||
|
|
||||||
|
public get pathLabel(): string {
|
||||||
|
return typeof this._pathLabel === 'undefined' ? this.path : this._pathLabel
|
||||||
|
}
|
||||||
|
|
||||||
|
public get isValid(): boolean {
|
||||||
|
return this.version != null
|
||||||
|
}
|
||||||
|
|
||||||
|
public get version(): API | null {
|
||||||
|
if (this._api) return this._api
|
||||||
|
let api = this._api = this.getTypeScriptVersion(this.tsServerPath)
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
public get versionString(): string | null {
|
||||||
|
const version = this.version
|
||||||
|
return version ? version.versionString : null
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTypeScriptVersion(serverPath: string): API | undefined {
|
||||||
|
if (!fs.existsSync(serverPath)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const p = serverPath.split(path.sep)
|
||||||
|
if (p.length <= 2) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const p2 = p.slice(0, -2)
|
||||||
|
const modulePath = p2.join(path.sep)
|
||||||
|
let fileName = path.join(modulePath, 'package.json')
|
||||||
|
if (!fs.existsSync(fileName)) {
|
||||||
|
// Special case for ts dev versions
|
||||||
|
if (path.basename(modulePath) === 'built') {
|
||||||
|
fileName = path.join(modulePath, '..', 'package.json')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(fileName)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const contents = fs.readFileSync(fileName).toString()
|
||||||
|
let desc: any = null
|
||||||
|
try {
|
||||||
|
desc = JSON.parse(contents)
|
||||||
|
} catch (err) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (!desc || !desc.version) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return desc.version ? API.fromVersionString(desc.version) : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TypeScriptVersionProvider {
|
||||||
|
|
||||||
|
public constructor(private configuration: TypeScriptServiceConfiguration) { }
|
||||||
|
|
||||||
|
public updateConfiguration(
|
||||||
|
configuration: TypeScriptServiceConfiguration
|
||||||
|
): void {
|
||||||
|
this.configuration = configuration
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getDefaultVersion(): Promise<TypeScriptVersion> {
|
||||||
|
// tsdk from configuration
|
||||||
|
let { globalTsdk } = this.configuration
|
||||||
|
if (globalTsdk) return new TypeScriptVersion(globalTsdk)
|
||||||
|
// resolve global module
|
||||||
|
let modulePath = await workspace.resolveModule('typescript', 'tsserver')
|
||||||
|
if (modulePath) {
|
||||||
|
let p = path.join(modulePath, 'lib')
|
||||||
|
return new TypeScriptVersion(p)
|
||||||
|
}
|
||||||
|
// use bundled
|
||||||
|
return this.bundledVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
public get globalVersion(): TypeScriptVersion | undefined {
|
||||||
|
let { globalTsdk } = this.configuration
|
||||||
|
if (globalTsdk) return new TypeScriptVersion(globalTsdk)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLocalVersion(root): TypeScriptVersion | undefined {
|
||||||
|
let paths = getParentDirs(root)
|
||||||
|
paths.unshift(root)
|
||||||
|
for (let p of paths) {
|
||||||
|
if (fs.existsSync(path.join(p, 'node_modules'))) {
|
||||||
|
let lib = path.join(p, 'node_modules/typescript/lib')
|
||||||
|
return new TypeScriptVersion(lib)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
public get bundledVersion(): TypeScriptVersion | null {
|
||||||
|
let file = path.join(workspace.pluginRoot, 'node_modules/typescript/lib/tsserver.js')
|
||||||
|
if (!fs.existsSync(file)) return null
|
||||||
|
try {
|
||||||
|
const bundledVersion = new TypeScriptVersion(
|
||||||
|
path.dirname(file),
|
||||||
|
''
|
||||||
|
)
|
||||||
|
return bundledVersion
|
||||||
|
} catch (e) {
|
||||||
|
// noop
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
138
src/server/utils/wireProtocol.ts
Normal file
138
src/server/utils/wireProtocol.ts
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
import stream from 'stream'
|
||||||
|
|
||||||
|
const DefaultSize = 8192
|
||||||
|
const ContentLength = 'Content-Length: '
|
||||||
|
const ContentLengthSize: number = Buffer.byteLength(ContentLength, 'utf8')
|
||||||
|
const Blank: number = Buffer.from(' ', 'utf8')[0]
|
||||||
|
const BackslashR: number = Buffer.from('\r', 'utf8')[0]
|
||||||
|
const BackslashN: number = Buffer.from('\n', 'utf8')[0]
|
||||||
|
|
||||||
|
class ProtocolBuffer {
|
||||||
|
private index = 0
|
||||||
|
private buffer: Buffer = Buffer.allocUnsafe(DefaultSize)
|
||||||
|
|
||||||
|
public append(data: string | Buffer): void {
|
||||||
|
let toAppend: Buffer | null = null
|
||||||
|
if (Buffer.isBuffer(data)) {
|
||||||
|
toAppend = data as Buffer
|
||||||
|
} else {
|
||||||
|
toAppend = Buffer.from(data, 'utf8')
|
||||||
|
}
|
||||||
|
if (this.buffer.length - this.index >= toAppend.length) {
|
||||||
|
toAppend.copy(this.buffer, this.index, 0, toAppend.length)
|
||||||
|
} else {
|
||||||
|
let newSize =
|
||||||
|
(Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) *
|
||||||
|
DefaultSize
|
||||||
|
if (this.index === 0) {
|
||||||
|
this.buffer = Buffer.allocUnsafe(newSize)
|
||||||
|
toAppend.copy(this.buffer, 0, 0, toAppend.length)
|
||||||
|
} else {
|
||||||
|
this.buffer = Buffer.concat(
|
||||||
|
[this.buffer.slice(0, this.index), toAppend],
|
||||||
|
newSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.index += toAppend.length
|
||||||
|
}
|
||||||
|
|
||||||
|
public tryReadContentLength(): number {
|
||||||
|
let result = -1
|
||||||
|
let current = 0
|
||||||
|
// we are utf8 encoding...
|
||||||
|
while (
|
||||||
|
current < this.index &&
|
||||||
|
(this.buffer[current] === Blank ||
|
||||||
|
this.buffer[current] === BackslashR ||
|
||||||
|
this.buffer[current] === BackslashN)
|
||||||
|
) {
|
||||||
|
current++
|
||||||
|
}
|
||||||
|
if (this.index < current + ContentLengthSize) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
current += ContentLengthSize
|
||||||
|
let start = current
|
||||||
|
while (current < this.index && this.buffer[current] !== BackslashR) {
|
||||||
|
current++
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
current + 3 >= this.index ||
|
||||||
|
this.buffer[current + 1] !== BackslashN ||
|
||||||
|
this.buffer[current + 2] !== BackslashR ||
|
||||||
|
this.buffer[current + 3] !== BackslashN
|
||||||
|
) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
let data = this.buffer.toString('utf8', start, current)
|
||||||
|
result = parseInt(data, 10)
|
||||||
|
this.buffer = this.buffer.slice(current + 4)
|
||||||
|
this.index = this.index - (current + 4)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
public tryReadContent(length: number): string | null {
|
||||||
|
if (this.index < length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
let result = this.buffer.toString('utf8', 0, length)
|
||||||
|
let sourceStart = length
|
||||||
|
while (
|
||||||
|
sourceStart < this.index &&
|
||||||
|
(this.buffer[sourceStart] === BackslashR ||
|
||||||
|
this.buffer[sourceStart] === BackslashN)
|
||||||
|
) {
|
||||||
|
sourceStart++
|
||||||
|
}
|
||||||
|
this.buffer.copy(this.buffer, 0, sourceStart)
|
||||||
|
this.index = this.index - sourceStart
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICallback<T> {
|
||||||
|
(data: T): void // tslint:disable-line
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Reader<T> {
|
||||||
|
private readonly buffer: ProtocolBuffer = new ProtocolBuffer()
|
||||||
|
private nextMessageLength = -1
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
private readonly readable: stream.Readable,
|
||||||
|
private readonly callback: ICallback<T>,
|
||||||
|
private readonly onError: (error: any) => void
|
||||||
|
) {
|
||||||
|
this.readable.on('data', (data: Buffer) => {
|
||||||
|
this.onLengthData(data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private onLengthData(data: Buffer): void {
|
||||||
|
try {
|
||||||
|
this.buffer.append(data)
|
||||||
|
while (true) {
|
||||||
|
if (this.nextMessageLength === -1) {
|
||||||
|
this.nextMessageLength = this.buffer.tryReadContentLength()
|
||||||
|
if (this.nextMessageLength === -1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const msg = this.buffer.tryReadContent(this.nextMessageLength)
|
||||||
|
if (msg === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.nextMessageLength = -1
|
||||||
|
const json = JSON.parse(msg)
|
||||||
|
this.callback(json)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
this.onError(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"extends": "./node_modules/@chemzqm/tsconfig/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "lib",
|
||||||
|
"target": "es2015",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"lib": ["es2018"],
|
||||||
|
"plugins": []
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
]
|
||||||
|
}
|
10
tslint.json
Normal file
10
tslint.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"extends": "./node_modules/@chemzqm/tslint-config/tslint.json",
|
||||||
|
"rules": {
|
||||||
|
},
|
||||||
|
"linterOptions": {
|
||||||
|
"exclude": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
644
yarn.lock
Normal file
644
yarn.lock
Normal file
|
@ -0,0 +1,644 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@babel/code-frame@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.44.tgz#2a02643368de80916162be70865c97774f3adbd9"
|
||||||
|
dependencies:
|
||||||
|
"@babel/highlight" "7.0.0-beta.44"
|
||||||
|
|
||||||
|
"@babel/generator@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.44.tgz#c7e67b9b5284afcf69b309b50d7d37f3e5033d42"
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "7.0.0-beta.44"
|
||||||
|
jsesc "^2.5.1"
|
||||||
|
lodash "^4.2.0"
|
||||||
|
source-map "^0.5.0"
|
||||||
|
trim-right "^1.0.1"
|
||||||
|
|
||||||
|
"@babel/helper-function-name@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.44.tgz#e18552aaae2231100a6e485e03854bc3532d44dd"
|
||||||
|
dependencies:
|
||||||
|
"@babel/helper-get-function-arity" "7.0.0-beta.44"
|
||||||
|
"@babel/template" "7.0.0-beta.44"
|
||||||
|
"@babel/types" "7.0.0-beta.44"
|
||||||
|
|
||||||
|
"@babel/helper-get-function-arity@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.44.tgz#d03ca6dd2b9f7b0b1e6b32c56c72836140db3a15"
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "7.0.0-beta.44"
|
||||||
|
|
||||||
|
"@babel/helper-split-export-declaration@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0-beta.44.tgz#c0b351735e0fbcb3822c8ad8db4e583b05ebd9dc"
|
||||||
|
dependencies:
|
||||||
|
"@babel/types" "7.0.0-beta.44"
|
||||||
|
|
||||||
|
"@babel/highlight@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.44.tgz#18c94ce543916a80553edcdcf681890b200747d5"
|
||||||
|
dependencies:
|
||||||
|
chalk "^2.0.0"
|
||||||
|
esutils "^2.0.2"
|
||||||
|
js-tokens "^3.0.0"
|
||||||
|
|
||||||
|
"@babel/template@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.44.tgz#f8832f4fdcee5d59bf515e595fc5106c529b394f"
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "7.0.0-beta.44"
|
||||||
|
"@babel/types" "7.0.0-beta.44"
|
||||||
|
babylon "7.0.0-beta.44"
|
||||||
|
lodash "^4.2.0"
|
||||||
|
|
||||||
|
"@babel/traverse@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.44.tgz#a970a2c45477ad18017e2e465a0606feee0d2966"
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "7.0.0-beta.44"
|
||||||
|
"@babel/generator" "7.0.0-beta.44"
|
||||||
|
"@babel/helper-function-name" "7.0.0-beta.44"
|
||||||
|
"@babel/helper-split-export-declaration" "7.0.0-beta.44"
|
||||||
|
"@babel/types" "7.0.0-beta.44"
|
||||||
|
babylon "7.0.0-beta.44"
|
||||||
|
debug "^3.1.0"
|
||||||
|
globals "^11.1.0"
|
||||||
|
invariant "^2.2.0"
|
||||||
|
lodash "^4.2.0"
|
||||||
|
|
||||||
|
"@babel/types@7.0.0-beta.44":
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.44.tgz#6b1b164591f77dec0a0342aca995f2d046b3a757"
|
||||||
|
dependencies:
|
||||||
|
esutils "^2.0.2"
|
||||||
|
lodash "^4.2.0"
|
||||||
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@chemzqm/neovim@4.3.23":
|
||||||
|
version "4.3.23"
|
||||||
|
resolved "https://registry.yarnpkg.com/@chemzqm/neovim/-/neovim-4.3.23.tgz#85385db1bedde01593be5f5ea7cbf7db10a868aa"
|
||||||
|
dependencies:
|
||||||
|
babel-eslint "^8.2.6"
|
||||||
|
msgpack-lite "^0.1.26"
|
||||||
|
traverse "^0.6.6"
|
||||||
|
|
||||||
|
"@chemzqm/tsconfig@^0.0.3":
|
||||||
|
version "0.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@chemzqm/tsconfig/-/tsconfig-0.0.3.tgz#ce3480d15d8cec46a315488caa07c9fca819aecc"
|
||||||
|
|
||||||
|
"@chemzqm/tslint-config@^1.0.17":
|
||||||
|
version "1.0.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/@chemzqm/tslint-config/-/tslint-config-1.0.17.tgz#9365dc9bdece0927fdfa6c068e1c70524c35a5fc"
|
||||||
|
dependencies:
|
||||||
|
tslint-config-prettier "^1.6.0"
|
||||||
|
tslint-react "^3.2.0"
|
||||||
|
|
||||||
|
"@types/node@^10.9.4":
|
||||||
|
version "10.9.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.4.tgz#0f4cb2dc7c1de6096055357f70179043c33e9897"
|
||||||
|
|
||||||
|
ansi-regex@^2.0.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||||
|
|
||||||
|
ansi-styles@^2.2.1:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||||
|
|
||||||
|
ansi-styles@^3.2.1:
|
||||||
|
version "3.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||||
|
dependencies:
|
||||||
|
color-convert "^1.9.0"
|
||||||
|
|
||||||
|
argparse@^1.0.7:
|
||||||
|
version "1.0.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
|
||||||
|
dependencies:
|
||||||
|
sprintf-js "~1.0.2"
|
||||||
|
|
||||||
|
babel-code-frame@^6.22.0:
|
||||||
|
version "6.26.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
|
||||||
|
dependencies:
|
||||||
|
chalk "^1.1.3"
|
||||||
|
esutils "^2.0.2"
|
||||||
|
js-tokens "^3.0.2"
|
||||||
|
|
||||||
|
babel-eslint@^8.2.6:
|
||||||
|
version "8.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-8.2.6.tgz#6270d0c73205628067c0f7ae1693a9e797acefd9"
|
||||||
|
dependencies:
|
||||||
|
"@babel/code-frame" "7.0.0-beta.44"
|
||||||
|
"@babel/traverse" "7.0.0-beta.44"
|
||||||
|
"@babel/types" "7.0.0-beta.44"
|
||||||
|
babylon "7.0.0-beta.44"
|
||||||
|
eslint-scope "3.7.1"
|
||||||
|
eslint-visitor-keys "^1.0.0"
|
||||||
|
|
||||||
|
babylon@7.0.0-beta.44:
|
||||||
|
version "7.0.0-beta.44"
|
||||||
|
resolved "http://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.44.tgz#89159e15e6e30c5096e22d738d8c0af8a0e8ca1d"
|
||||||
|
|
||||||
|
balanced-match@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
|
||||||
|
brace-expansion@^1.1.7:
|
||||||
|
version "1.1.11"
|
||||||
|
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
|
||||||
|
dependencies:
|
||||||
|
balanced-match "^1.0.0"
|
||||||
|
concat-map "0.0.1"
|
||||||
|
|
||||||
|
bser@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719"
|
||||||
|
dependencies:
|
||||||
|
node-int64 "^0.4.0"
|
||||||
|
|
||||||
|
builtin-modules@^1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
|
||||||
|
|
||||||
|
chalk@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^2.2.1"
|
||||||
|
escape-string-regexp "^1.0.2"
|
||||||
|
has-ansi "^2.0.0"
|
||||||
|
strip-ansi "^3.0.0"
|
||||||
|
supports-color "^2.0.0"
|
||||||
|
|
||||||
|
chalk@^2.0.0, chalk@^2.3.0:
|
||||||
|
version "2.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^3.2.1"
|
||||||
|
escape-string-regexp "^1.0.5"
|
||||||
|
supports-color "^5.3.0"
|
||||||
|
|
||||||
|
circular-json@^0.5.5:
|
||||||
|
version "0.5.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.5.tgz#64182ef359042d37cd8e767fc9de878b1e9447d3"
|
||||||
|
|
||||||
|
coc.nvim@^0.0.15:
|
||||||
|
version "0.0.15"
|
||||||
|
resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.15.tgz#e28e42e9dae92bf372b16918bf68b2e8696e7e4d"
|
||||||
|
dependencies:
|
||||||
|
"@chemzqm/neovim" "4.3.23"
|
||||||
|
debounce "^1.2.0"
|
||||||
|
deep-equal "^1.0.1"
|
||||||
|
diff "^3.5.0"
|
||||||
|
fast-diff "^1.1.2"
|
||||||
|
fb-watchman "^2.0.0"
|
||||||
|
fuzzaldrin "^2.1.0"
|
||||||
|
glob "^7.1.3"
|
||||||
|
jsonc-parser "^2.0.2"
|
||||||
|
log4js "^3.0.5"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
node-serial "^0.1.1"
|
||||||
|
once "^1.4.0"
|
||||||
|
pify "^4.0.0"
|
||||||
|
semver "^5.5.1"
|
||||||
|
tslib "^1.9.3"
|
||||||
|
uuid "^3.3.2"
|
||||||
|
vscode-languageserver-protocol "^3.12.0"
|
||||||
|
vscode-languageserver-types "^3.12.0"
|
||||||
|
vscode-uri "^1.0.6"
|
||||||
|
which "^1.3.1"
|
||||||
|
|
||||||
|
color-convert@^1.9.0:
|
||||||
|
version "1.9.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||||
|
dependencies:
|
||||||
|
color-name "1.1.3"
|
||||||
|
|
||||||
|
color-name@1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
|
||||||
|
|
||||||
|
commander@^2.12.1:
|
||||||
|
version "2.17.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf"
|
||||||
|
|
||||||
|
concat-map@0.0.1:
|
||||||
|
version "0.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||||
|
|
||||||
|
core-util-is@~1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
|
||||||
|
date-format@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8"
|
||||||
|
|
||||||
|
debounce@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
|
||||||
|
|
||||||
|
debug@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
|
dependencies:
|
||||||
|
ms "2.0.0"
|
||||||
|
|
||||||
|
deep-equal@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5"
|
||||||
|
|
||||||
|
diff@^3.2.0, diff@^3.5.0:
|
||||||
|
version "3.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
|
||||||
|
|
||||||
|
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||||
|
|
||||||
|
eslint-scope@3.7.1:
|
||||||
|
version "3.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8"
|
||||||
|
dependencies:
|
||||||
|
esrecurse "^4.1.0"
|
||||||
|
estraverse "^4.1.1"
|
||||||
|
|
||||||
|
eslint-visitor-keys@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
|
||||||
|
|
||||||
|
esprima@^4.0.0:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
|
||||||
|
|
||||||
|
esrecurse@^4.1.0:
|
||||||
|
version "4.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf"
|
||||||
|
dependencies:
|
||||||
|
estraverse "^4.1.0"
|
||||||
|
|
||||||
|
estraverse@^4.1.0, estraverse@^4.1.1:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
|
||||||
|
|
||||||
|
esutils@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||||
|
|
||||||
|
event-lite@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/event-lite/-/event-lite-0.1.1.tgz#47cf08a8d37d0b694cdb7b3b17b51faac6576086"
|
||||||
|
|
||||||
|
fast-diff@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.2.tgz#4b62c42b8e03de3f848460b639079920695d0154"
|
||||||
|
|
||||||
|
fb-watchman@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58"
|
||||||
|
dependencies:
|
||||||
|
bser "^2.0.0"
|
||||||
|
|
||||||
|
fs.realpath@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
||||||
|
|
||||||
|
fuzzaldrin@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fuzzaldrin/-/fuzzaldrin-2.1.0.tgz#90204c3e2fdaa6941bb28d16645d418063a90e9b"
|
||||||
|
|
||||||
|
glob@^7.0.5, glob@^7.1.1, glob@^7.1.3:
|
||||||
|
version "7.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1"
|
||||||
|
dependencies:
|
||||||
|
fs.realpath "^1.0.0"
|
||||||
|
inflight "^1.0.4"
|
||||||
|
inherits "2"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
once "^1.3.0"
|
||||||
|
path-is-absolute "^1.0.0"
|
||||||
|
|
||||||
|
globals@^11.1.0:
|
||||||
|
version "11.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673"
|
||||||
|
|
||||||
|
has-ansi@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^2.0.0"
|
||||||
|
|
||||||
|
has-flag@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
|
||||||
|
|
||||||
|
ieee754@^1.1.8:
|
||||||
|
version "1.1.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||||
|
|
||||||
|
inflight@^1.0.4:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
||||||
|
dependencies:
|
||||||
|
once "^1.3.0"
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
|
inherits@2, inherits@~2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||||
|
|
||||||
|
int64-buffer@^0.1.9:
|
||||||
|
version "0.1.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.10.tgz#277b228a87d95ad777d07c13832022406a473423"
|
||||||
|
|
||||||
|
invariant@^2.2.0:
|
||||||
|
version "2.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.0.0"
|
||||||
|
|
||||||
|
isarray@^1.0.0, isarray@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
|
|
||||||
|
isexe@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
|
|
||||||
|
js-tokens@^3.0.0, js-tokens@^3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||||
|
|
||||||
|
"js-tokens@^3.0.0 || ^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
|
||||||
|
js-yaml@^3.7.0:
|
||||||
|
version "3.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1"
|
||||||
|
dependencies:
|
||||||
|
argparse "^1.0.7"
|
||||||
|
esprima "^4.0.0"
|
||||||
|
|
||||||
|
jsesc@^2.5.1:
|
||||||
|
version "2.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.1.tgz#e421a2a8e20d6b0819df28908f782526b96dd1fe"
|
||||||
|
|
||||||
|
jsonc-parser@^2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.2.tgz#42fcf56d70852a043fadafde51ddb4a85649978d"
|
||||||
|
|
||||||
|
lodash@^4.2.0:
|
||||||
|
version "4.17.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
||||||
|
|
||||||
|
log4js@^3.0.5:
|
||||||
|
version "3.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/log4js/-/log4js-3.0.5.tgz#b80146bfebad68b430d4f3569556d8a6edfef303"
|
||||||
|
dependencies:
|
||||||
|
circular-json "^0.5.5"
|
||||||
|
date-format "^1.2.0"
|
||||||
|
debug "^3.1.0"
|
||||||
|
rfdc "^1.1.2"
|
||||||
|
streamroller "0.7.0"
|
||||||
|
|
||||||
|
loose-envify@^1.0.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
|
dependencies:
|
||||||
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
|
minimatch@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
||||||
|
dependencies:
|
||||||
|
brace-expansion "^1.1.7"
|
||||||
|
|
||||||
|
minimist@0.0.8:
|
||||||
|
version "0.0.8"
|
||||||
|
resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
|
||||||
|
mkdirp@^0.5.1:
|
||||||
|
version "0.5.1"
|
||||||
|
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||||
|
dependencies:
|
||||||
|
minimist "0.0.8"
|
||||||
|
|
||||||
|
ms@2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
|
|
||||||
|
msgpack-lite@^0.1.26:
|
||||||
|
version "0.1.26"
|
||||||
|
resolved "https://registry.yarnpkg.com/msgpack-lite/-/msgpack-lite-0.1.26.tgz#dd3c50b26f059f25e7edee3644418358e2a9ad89"
|
||||||
|
dependencies:
|
||||||
|
event-lite "^0.1.1"
|
||||||
|
ieee754 "^1.1.8"
|
||||||
|
int64-buffer "^0.1.9"
|
||||||
|
isarray "^1.0.0"
|
||||||
|
|
||||||
|
next-tick@~0.1.0:
|
||||||
|
version "0.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-0.1.0.tgz#1912cce8eb9b697d640fbba94f8f00dec3b94259"
|
||||||
|
|
||||||
|
node-int64@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
|
||||||
|
|
||||||
|
node-serial@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/node-serial/-/node-serial-0.1.1.tgz#3766fa9614c20c6f27d3713f9bd8c56747fbb8c9"
|
||||||
|
dependencies:
|
||||||
|
next-tick "~0.1.0"
|
||||||
|
|
||||||
|
once@^1.3.0, once@^1.4.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||||
|
dependencies:
|
||||||
|
wrappy "1"
|
||||||
|
|
||||||
|
path-is-absolute@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||||
|
|
||||||
|
path-parse@^1.0.5:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
|
||||||
|
|
||||||
|
pify@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.0.tgz#db04c982b632fd0df9090d14aaf1c8413cadb695"
|
||||||
|
|
||||||
|
process-nextick-args@~2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||||
|
|
||||||
|
readable-stream@^2.3.0:
|
||||||
|
version "2.3.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||||
|
dependencies:
|
||||||
|
core-util-is "~1.0.0"
|
||||||
|
inherits "~2.0.3"
|
||||||
|
isarray "~1.0.0"
|
||||||
|
process-nextick-args "~2.0.0"
|
||||||
|
safe-buffer "~5.1.1"
|
||||||
|
string_decoder "~1.1.1"
|
||||||
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
|
resolve@^1.3.2:
|
||||||
|
version "1.8.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26"
|
||||||
|
dependencies:
|
||||||
|
path-parse "^1.0.5"
|
||||||
|
|
||||||
|
rfdc@^1.1.2:
|
||||||
|
version "1.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
|
||||||
|
|
||||||
|
rimraf@^2.6.2:
|
||||||
|
version "2.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
|
||||||
|
dependencies:
|
||||||
|
glob "^7.0.5"
|
||||||
|
|
||||||
|
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
|
version "5.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
|
||||||
|
semver@^5.3.0, semver@^5.5.1:
|
||||||
|
version "5.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
|
||||||
|
|
||||||
|
source-map@^0.5.0:
|
||||||
|
version "0.5.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||||
|
|
||||||
|
sprintf-js@~1.0.2:
|
||||||
|
version "1.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
||||||
|
|
||||||
|
streamroller@0.7.0:
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b"
|
||||||
|
dependencies:
|
||||||
|
date-format "^1.2.0"
|
||||||
|
debug "^3.1.0"
|
||||||
|
mkdirp "^0.5.1"
|
||||||
|
readable-stream "^2.3.0"
|
||||||
|
|
||||||
|
string_decoder@~1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "~5.1.0"
|
||||||
|
|
||||||
|
strip-ansi@^3.0.0:
|
||||||
|
version "3.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^2.0.0"
|
||||||
|
|
||||||
|
supports-color@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
||||||
|
|
||||||
|
supports-color@^5.3.0:
|
||||||
|
version "5.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
|
||||||
|
dependencies:
|
||||||
|
has-flag "^3.0.0"
|
||||||
|
|
||||||
|
to-fast-properties@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
|
||||||
|
|
||||||
|
traverse@^0.6.6:
|
||||||
|
version "0.6.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137"
|
||||||
|
|
||||||
|
trim-right@^1.0.1:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003"
|
||||||
|
|
||||||
|
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.3:
|
||||||
|
version "1.9.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||||
|
|
||||||
|
tslint-config-prettier@^1.6.0:
|
||||||
|
version "1.15.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslint-config-prettier/-/tslint-config-prettier-1.15.0.tgz#76b9714399004ab6831fdcf76d89b73691c812cf"
|
||||||
|
|
||||||
|
tslint-react@^3.2.0:
|
||||||
|
version "3.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslint-react/-/tslint-react-3.6.0.tgz#7f462c95c4a0afaae82507f06517ff02942196a1"
|
||||||
|
dependencies:
|
||||||
|
tsutils "^2.13.1"
|
||||||
|
|
||||||
|
tslint@^5.11.0:
|
||||||
|
version "5.11.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed"
|
||||||
|
dependencies:
|
||||||
|
babel-code-frame "^6.22.0"
|
||||||
|
builtin-modules "^1.1.1"
|
||||||
|
chalk "^2.3.0"
|
||||||
|
commander "^2.12.1"
|
||||||
|
diff "^3.2.0"
|
||||||
|
glob "^7.1.1"
|
||||||
|
js-yaml "^3.7.0"
|
||||||
|
minimatch "^3.0.4"
|
||||||
|
resolve "^1.3.2"
|
||||||
|
semver "^5.3.0"
|
||||||
|
tslib "^1.8.0"
|
||||||
|
tsutils "^2.27.2"
|
||||||
|
|
||||||
|
tsutils@^2.13.1, tsutils@^2.27.2:
|
||||||
|
version "2.29.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.29.0.tgz#32b488501467acbedd4b85498673a0812aca0b99"
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.8.1"
|
||||||
|
|
||||||
|
typescript@^3.0.3:
|
||||||
|
version "3.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.3.tgz#4853b3e275ecdaa27f78fda46dc273a7eb7fc1c8"
|
||||||
|
|
||||||
|
util-deprecate@~1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
|
|
||||||
|
uuid@^3.3.2:
|
||||||
|
version "3.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||||
|
|
||||||
|
vscode-jsonrpc@^3.6.2:
|
||||||
|
version "3.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.6.2.tgz#3b5eef691159a15556ecc500e9a8a0dd143470c8"
|
||||||
|
|
||||||
|
vscode-languageserver-protocol@^3.12.0:
|
||||||
|
version "3.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.12.0.tgz#5b23501292abad88f0463b01e83ff98e64a37652"
|
||||||
|
dependencies:
|
||||||
|
vscode-jsonrpc "^3.6.2"
|
||||||
|
vscode-languageserver-types "^3.12.0"
|
||||||
|
|
||||||
|
vscode-languageserver-types@^3.12.0:
|
||||||
|
version "3.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.12.0.tgz#f96051381b6a050b7175b37d6cb5d2f2eb64b944"
|
||||||
|
|
||||||
|
vscode-uri@^1.0.6:
|
||||||
|
version "1.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.6.tgz#6b8f141b0bbc44ad7b07e94f82f168ac7608ad4d"
|
||||||
|
|
||||||
|
which@^1.3.1:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||||
|
dependencies:
|
||||||
|
isexe "^2.0.0"
|
||||||
|
|
||||||
|
wrappy@1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
Loading…
Add table
Reference in a new issue