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