This commit is contained in:
chemzqm 2018-09-07 20:40:51 +08:00
commit 54d03a04c1
63 changed files with 10429 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
lib

4
.npmignore Normal file
View file

@ -0,0 +1,4 @@
src
tsconfig.json
tslint.json
*.map

50
Readme.md Normal file
View 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
View 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
View 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
View 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
View 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
}

View 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)
}
}
}

View 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()
}
}

View 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
}
}

View 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)
}
}

View 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
})
}
}

View 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 []
}
}

View 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
}
}

View 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
}

View 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'
}
}

View 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
}
}
}

View 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 []
}
}

View 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
}
}

View 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
}
}

View 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)
}
}

View 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)
}
}

View 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
}
}

View 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
}
}

View 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 []
}
}
}

View 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
}
}

View 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 }
}
}

View 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)
}

View 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, [])
}
})
}
}
}

View 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)
}
}

View 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)
}
}

View 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)
}
}

View 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
View 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
}
}

View 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
)
}
}

View 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

File diff suppressed because it is too large Load diff

298
src/server/schema.json Normal file
View 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
}
}
}

View 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>
}

View 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')
}

View 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
View 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
View 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
}
}
}

View 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
}

View 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
}

View 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
View 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
View 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]'
}

View 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'
}
]

View 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]

View 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))
}
}
}

View 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
View 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)
})
}

View 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
View 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)
}
}
}

View 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
}

View 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 }
}
}

View 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
}
}

View 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
}
}

View 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
View 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
View file

@ -0,0 +1,10 @@
{
"extends": "./node_modules/@chemzqm/tslint-config/tslint.json",
"rules": {
},
"linterOptions": {
"exclude": [
]
}
}

644
yarn.lock Normal file
View 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"