rework of typescriptService, support interuptGetErr

This commit is contained in:
chemzqm 2019-03-12 18:48:45 +08:00
parent 77690fb036
commit cbb489c462
31 changed files with 523 additions and 443 deletions

51
src/server/callbackMap.ts Normal file
View file

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* 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 { ServerResponse } from './typescriptService'
export interface CallbackItem<R> {
readonly onSuccess: (value: R) => void
readonly onError: (err: Error) => void
readonly startTime: number
readonly isAsync: boolean
}
export class CallbackMap<R extends Proto.Response> {
private readonly _callbacks = new Map<number, CallbackItem<ServerResponse.Response<R> | undefined>>()
private readonly _asyncCallbacks = new Map<number, CallbackItem<ServerResponse.Response<R> | undefined>>()
public destroy(cause: string): void {
const cancellation = new ServerResponse.Cancelled(cause)
for (const callback of this._callbacks.values()) {
callback.onSuccess(cancellation)
}
this._callbacks.clear()
for (const callback of this._asyncCallbacks.values()) {
callback.onSuccess(cancellation)
}
this._asyncCallbacks.clear()
}
public add(seq: number, callback: CallbackItem<ServerResponse.Response<R> | undefined>, isAsync: boolean): void {
if (isAsync) {
this._asyncCallbacks.set(seq, callback)
} else {
this._callbacks.set(seq, callback)
}
}
public fetch(seq: number): CallbackItem<ServerResponse.Response<R> | undefined> | undefined {
const callback = this._callbacks.get(seq) || this._asyncCallbacks.get(seq)
this.delete(seq)
return callback
}
private delete(seq: number): void {
if (!this._callbacks.delete(seq)) {
this._asyncCallbacks.delete(seq)
}
}
}

View file

@ -59,7 +59,7 @@ async function goToProjectConfig(clientHost: TypeScriptServiceClientHost, uri: s
}
const client = clientHost.serviceClient
const file = client.toPath(uri)
let res: Proto.ProjectInfoResponse | undefined
let res
try {
res = await client.execute('projectInfo', { file, needFileNameList: false }, CancellationToken.None)
} catch {
@ -132,7 +132,7 @@ export class AutoFixCommand implements Command {
...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+(diagnostic.code!)]
}
const response: Proto.GetCodeFixesResponse = await client.execute('getCodeFixes', args)
const response = await client.execute('getCodeFixes', args, CancellationToken.None)
if (response.type !== 'response' || !response.body || response.body.length < 1) {
if (diagnostic.code == 2304) {
let { range } = diagnostic
@ -162,7 +162,7 @@ export class AutoFixCommand implements Command {
}
}
if (edits.length) await document.applyEdits(workspace.nvim, edits)
if (command) await commands.executeCommand(command)
if (command) commands.executeCommand(command)
}
}
@ -173,7 +173,7 @@ export class ConfigurePluginCommand implements Command {
private readonly pluginManager: PluginManager,
) { }
public execute(pluginId: string, configuration: any) {
public execute(pluginId: string, configuration: any): void {
this.pluginManager.setConfiguration(pluginId, configuration)
}
}

View file

@ -63,7 +63,7 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider
try {
const response = await this.cachedResponse.execute(document, () =>
this.client.execute('navtree', { file: filepath }, token)
this.client.execute('navtree', { file: filepath }, token) as any
)
if (!response) {
return []

View file

@ -2,8 +2,8 @@
* 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 { CancellationTokenSource, DidChangeTextDocumentParams, Disposable, TextDocument } from 'vscode-languageserver-protocol'
import Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
@ -40,6 +40,7 @@ export default class BufferSyncSupport {
private readonly pendingDiagnostics = new Map<string, number>()
private readonly diagnosticDelayer: Delayer<any>
private pendingGetErr: GetErrRequest | undefined
constructor(
client: ITypeScriptServiceClient,
@ -105,7 +106,7 @@ export default class BufferSyncSupport {
if (root) args.projectRootPath = root
}
this.client.execute('open', args, false) // tslint:disable-line
this.client.executeWithoutWaitingForResponse('open', args) // tslint:disable-line
this.requestDiagnostic(uri)
}
@ -116,7 +117,7 @@ export default class BufferSyncSupport {
const args: Proto.FileRequestArgs = {
file: filepath
}
this.client.execute('close', args, false) // tslint:disable-line
this.client.executeWithoutWaitingForResponse('close', args) // tslint:disable-line
}
private onDidChangeTextDocument(e: DidChangeTextDocumentParams): void {
@ -133,9 +134,33 @@ export default class BufferSyncSupport {
endOffset: range ? range.end.character + 1 : 1,
insertString: text
}
this.client.execute('change', args, false) // tslint:disable-line
this.client.executeWithoutWaitingForResponse('change', args) // tslint:disable-line
}
this.requestDiagnostic(uri)
const didTrigger = this.requestDiagnostic(uri)
if (!didTrigger && this.pendingGetErr) {
// In this case we always want to re-trigger all diagnostics
this.pendingGetErr.cancel()
this.pendingGetErr = undefined
this.triggerDiagnostics()
}
}
public interuptGetErr<R>(f: () => R): R {
if (!this.pendingGetErr) {
return f()
}
this.pendingGetErr.cancel()
this.pendingGetErr = undefined
const result = f()
this.triggerDiagnostics()
return result
}
private triggerDiagnostics(delay = 200): void {
this.diagnosticDelayer.trigger(() => {
this.sendPendingDiagnostics()
}, delay)
}
public requestAllDiagnostics(): void {
@ -150,19 +175,17 @@ export default class BufferSyncSupport {
}, 200)
}
public requestDiagnostic(uri: string): void {
public requestDiagnostic(uri: string): boolean {
if (!this._validate) {
return
return false
}
let document = workspace.getDocument(uri)
if (!document) return
if (!document) return false
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
const delay = Math.min(Math.max(Math.ceil(lineCount / 20), 300), 800)
this.triggerDiagnostics(delay)
return true
}
public hasPendingDiagnostics(uri: string): boolean {
@ -173,24 +196,68 @@ export default class BufferSyncSupport {
if (!this._validate) {
return
}
const files = Array.from(this.pendingDiagnostics.entries())
const uris = Array.from(this.pendingDiagnostics.entries())
.sort((a, b) => a[1] - b[1])
.map(entry => this.client.toPath(entry[0]))
.map(entry => 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 (uris.indexOf(uri) == -1) {
uris.push(uri)
}
}
let files = uris.map(uri => this.client.toPath(uri))
if (files.length) {
const args: Proto.GeterrRequestArgs = {
delay: 0,
files
}
this.client.execute('geterr', args, false) // tslint:disable-line
if (this.pendingGetErr) this.pendingGetErr.cancel()
const getErr = this.pendingGetErr = GetErrRequest.executeGetErrRequest(this.client, files, () => {
if (this.pendingGetErr === getErr) {
this.pendingGetErr = undefined
}
})
}
this.pendingDiagnostics.clear()
}
}
class GetErrRequest {
public static executeGetErrRequest(
client: ITypeScriptServiceClient,
files: string[],
onDone: () => void
): GetErrRequest {
const token = new CancellationTokenSource()
return new GetErrRequest(client, files, token, onDone)
}
private _done = false
private constructor(
client: ITypeScriptServiceClient,
public readonly files: string[],
private readonly _token: CancellationTokenSource,
onDone: () => void
) {
const args: Proto.GeterrRequestArgs = {
delay: 0,
files: this.files
}
const done = () => {
if (this._done) {
return
}
this._done = true
onDone()
}
client.executeAsync('geterr', args, _token.token).then(done, done)
}
public cancel(): any {
if (!this._done) {
this._token.cancel()
}
this._token.dispose()
}
}

View file

@ -7,7 +7,7 @@ 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 { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'
import API from '../utils/api'
import { applyCodeAction } from '../utils/codeAction'
import { convertCompletionEntry, getParameterListParts } from '../utils/completionItem'
@ -16,6 +16,7 @@ import * as typeConverters from '../utils/typeConverters'
import TypingsStatus from '../utils/typingsStatus'
import FileConfigurationManager, { SuggestOptions } from './fileConfigurationManager'
import SnippetString from '../utils/SnippetString'
import BufferSyncSupport from './bufferSyncSupport'
// command center
export interface CommandItem {
@ -58,6 +59,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
private readonly client: ITypeScriptServiceClient,
private readonly typingsStatus: TypingsStatus,
private readonly fileConfigurationManager: FileConfigurationManager,
private readonly bufferSyncSupport: BufferSyncSupport,
languageId: string
) {
@ -90,8 +92,13 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
context: CompletionContext,
): Promise<CompletionList | null> {
if (this.typingsStatus.isAcquiringTypings) {
workspace.showMessage('Acquiring typings...', 'warning')
return null
return Promise.resolve({
isIncomplete: true,
items: [{
label: 'Acquiring typings...',
detail: 'Acquiring typings definitions for IntelliSense.'
}]
})
}
let { uri } = document
const file = this.client.toPath(document.uri)
@ -121,7 +128,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
let isNewIdentifierLocation = true
if (this.client.apiVersion.gte(API.v300)) {
try {
const response = await this.client.execute('completionInfo', args, token)
const response = await this.bufferSyncSupport.interuptGetErr(() => this.client.execute('completionInfo', args, token))
if (response.type !== 'response' || !response.body) {
return null
}
@ -134,9 +141,11 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
throw e
}
} else {
const response = await this.client.execute('completions', args, token)
const response = await this.bufferSyncSupport.interuptGetErr(() => this.client.execute('completions', args, token))
if (response.type !== 'response' || !response.body) {
return null
}
msg = response.body
if (!msg) return null
}
const completionItems: CompletionItem[] = []
@ -207,7 +216,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
]
}
let response: Proto.CompletionDetailsResponse
let response: ServerResponse.Response<Proto.CompletionDetailsResponse>
try {
response = await this.client.execute(
'completionEntryDetails',
@ -217,6 +226,9 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
} catch {
return item
}
if (response.type !== 'response' || !response.body) {
return item
}
const details = response.body
if (!details || !details.length || !details[0]) {
@ -237,7 +249,6 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
this.createSnippetOfFunctionCall(item, detail)
}
}
return item
}
@ -319,7 +330,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
return false
}
} else if (triggerCharacter === '<') {
return this.client.apiVersion.gte(API.v290)
return false
}
return true
@ -411,7 +422,7 @@ function appendJoinedPlaceholders(
snippet: SnippetString,
parts: ReadonlyArray<Proto.SymbolDisplayPart>,
joiner: string
) {
): void {
for (let i = 0; i < parts.length; ++i) {
const paramterPart = parts[i]
snippet.appendPlaceholder(paramterPart.text)

View file

@ -15,7 +15,7 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider,
definitionType: 'definition' | 'implementation' | 'typeDefinition',
document: TextDocument,
position: Position,
token: CancellationToken | boolean
token: CancellationToken
): Promise<Location[] | undefined> {
const filepath = this.client.toPath(document.uri)
if (!filepath) {
@ -28,7 +28,7 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider,
)
try {
const response = await this.client.execute(definitionType, args, token)
const locations: Proto.FileSpan[] = (response && response.body) || []
const locations: Proto.FileSpan[] = (response.type == 'response' && response.body) || []
return locations.map(location =>
typeConverters.Location.fromTextSpan(
this.client.toResource(location.file),
@ -43,7 +43,7 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider,
public provideDefinition(
document: TextDocument,
position: Position,
token: CancellationToken | boolean
token: CancellationToken
): Promise<Definition | undefined> {
return this.getSymbolLocations('definition', document, position, token)
}

View file

@ -25,7 +25,7 @@ export default class TypeScriptDocumentHighlightProvider implements DocumentHigh
)
try {
const response = await this.client.execute('occurrences', args, token)
if (response && response.body) {
if (response.type == 'response' && response.body) {
return response.body
.filter(x => !x.isInString)
.map(documentHighlightFromOccurance)

View file

@ -60,7 +60,7 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP
try {
const response = await this.client.execute('navtree', args, token)
if (response.body) {
if (response.type == 'response' && response.body) {
// The root represents the file. Ignore this when showing in the UI
const tree = response.body
if (tree.childItems) {

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'vscode-languageserver-protocol'
import { TextDocument, CancellationToken } from 'vscode-languageserver-protocol'
import { WorkspaceConfiguration, workspace } from 'coc.nvim'
import Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
@ -58,7 +58,7 @@ export default class FileConfigurationManager {
hostInfo: 'nvim-coc',
...currentOptions
} as Proto.ConfigureRequestArguments
await this.client.execute('configure', args)
await this.client.execute('configure', args, CancellationToken.None)
this.cachedOption = options
this.requesting = false
}

View file

@ -25,7 +25,11 @@ export default class TypeScriptFoldingProvider implements FoldingRangeProvider {
}
const args: Proto.FileRequestArgs = { file }
const { body } = await this.client.execute('getOutliningSpans', args, token)
const res = await this.client.execute('getOutliningSpans', args, token)
if (res.type != 'response') {
return
}
const { body } = res
if (!body) {
return
}

View file

@ -34,7 +34,7 @@ export default class TypeScriptFormattingProvider
)
try {
const response = await this.client.execute('format', args, token)
if (response.body) {
if (response.type == 'response' && response.body) {
let edits = response.body.map(typeConverters.TextEdit.fromCodeEdit)
if (this.formattingOptionsManager.removeSemicolons(document.languageId)) {
return removeSemicolon(document, edits)
@ -109,7 +109,11 @@ export default class TypeScriptFormattingProvider
key: ch
}
try {
const { body } = await this.client.execute('formatonkey', args, token)
const res = await this.client.execute('formatonkey', args, token)
if (res.type != 'response') {
return []
}
const { body } = res
const edits = body
const result: TextEdit[] = []
if (!edits) {

View file

@ -27,7 +27,7 @@ export default class TypeScriptHoverProvider implements HoverProvider {
)
try {
const response = await this.client.execute('quickinfo', args, token)
if (response && response.body) {
if (response && response.type == 'response' && response.body) {
const data = response.body
return {
contents: TypeScriptHoverProvider.getContents(data),

View file

@ -21,8 +21,8 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip
codeLens.range.start
)
try {
const response = await this.client.execute('implementation', args, token)
if (response && response.body) {
const response = await this.client.execute('implementation', args, token, true)
if (response && response.type == 'response' && response.body) {
const locations = response.body
.map(reference => {
return {

View file

@ -1,6 +1,6 @@
import { disposeAll, workspace } from 'coc.nvim'
import { Command, CommandManager } from 'coc.nvim/lib/commands'
import { Disposable } from 'vscode-languageserver-protocol'
import { Disposable, CancellationToken } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
import * as languageIds from '../utils/languageModeIds'
@ -21,7 +21,8 @@ class ProjectErrorCommand implements Command {
file,
delay: 20
}
const response = await this.client.execute('geterrForProject', args)
const response = null
// await this.client.execute('geterrForProject', args, CancellationToken.None)
if (!response || !response.success) {
return
}

View file

@ -52,10 +52,11 @@ class ApplyFixAllCodeAction implements Command {
}
try {
const { body } = await this.client.execute('getCombinedCodeFix', args)
if (!body) {
const res = await this.client.execute('getCombinedCodeFix', args, CancellationToken.None)
if (res.type != 'response') {
return
}
let { body } = res
const edit = typeConverters.WorkspaceEdit.fromFileCodeEdits(
this.client,
@ -121,11 +122,16 @@ class SupportedCodeActionProvider {
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 new Promise<Set<number>>((resolve, reject) => {
this.client.execute('getSupportedCodeFixes', null, CancellationToken.None).then(res => {
if (res.type !== 'response') {
resolve(new Set())
return
}
let codes = res.body.map(code => +code).filter(code => !isNaN(code))
resolve(new Set(codes))
}, reject)
})
}
return Promise.resolve(this._supportedCodeActions)
}
@ -199,6 +205,9 @@ export default class TypeScriptQuickFixProvider implements CodeActionProvider {
args,
token
)
if (codeFixesResponse.type != 'response') {
return []
}
if (codeFixesResponse.body) {
const results: CodeAction[] = []
for (const tsCodeFix of codeFixesResponse.body) {

View file

@ -29,7 +29,7 @@ class ApplyRefactoringCommand implements Command {
refactor,
action
}
const response = await this.client.execute('getEditsForRefactor', args)
const response = await this.client.execute('getEditsForRefactor', args, CancellationToken.None) as any
const body = response && response.body
if (!body || !body.edits.length) {
return false
@ -125,7 +125,7 @@ export default class TypeScriptRefactorProvider implements CodeActionProvider {
file,
range
)
let response: Proto.GetApplicableRefactorsResponse
let response
try {
response = await this.client.execute('getApplicableRefactors', args, token)
if (!response || !response.body) {

View file

@ -26,7 +26,7 @@ export default class TypeScriptReferences implements ReferenceProvider {
)
try {
const msg = await this.client.execute('references', args, token)
if (!msg.body) {
if (!msg || msg.type != 'response' || !msg.body) {
return []
}
const result: Location[] = []

View file

@ -20,9 +20,9 @@ export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBase
codeLens.range.start
)
return this.client
.execute('references', args, token)
.execute('references', args, token, true)
.then(response => {
if (!response || !response.body) {
if (!response || response.type != 'response' || !response.body) {
throw codeLens
}

View file

@ -72,7 +72,7 @@ export default class TypeScriptRenameProvider implements RenameProvider {
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<ServerResponse<Proto.RenameResponse> | undefined> {
): Promise<ServerResponse.Response<Proto.RenameResponse> | undefined> {
const file = this.client.toPath(document.uri)
if (!file) return undefined

View file

@ -31,7 +31,7 @@ export default class TypeScriptSignatureHelpProvider implements SignatureHelpPro
let info: Proto.SignatureHelpItems | undefined
try {
const response = await this.client.execute('signatureHelp', args, token)
info = response.body
info = (response as any).body
if (!info) return undefined
} catch {
return undefined

View file

@ -29,7 +29,7 @@ export default class TypeScriptTagCompletion implements CompletionItemProvider {
let body: Proto.TextInsertion | undefined
try {
const response = await this.client.execute('jsxClosingTag', args, token)
body = response && response.body
body = response && (response as any).body
if (!body) {
return undefined
}

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, TextDocument, WorkspaceEdit } from 'vscode-languageserver-protocol'
import { Disposable, TextDocument, WorkspaceEdit, CancellationToken } from 'vscode-languageserver-protocol'
import Uri from 'vscode-uri'
import { disposeAll, workspace } from 'coc.nvim'
import * as Proto from '../protocol'
@ -79,8 +79,8 @@ export default class UpdateImportsOnFileRenameHandler {
oldFilePath: oldFile,
newFilePath: newFile
}
const response = await this.client.execute('getEditsForFileRename', args)
if (!response || !response.body) {
const response = await this.client.execute('getEditsForFileRename', args, CancellationToken.None)
if (!response || response.type != 'response' || !response.body) {
return
}

View file

@ -50,7 +50,7 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo
}
const response = await this.client.execute('navto', args, token)
if (!response.body) return []
if (response.type !== 'response' || response.body == null) return []
const result: SymbolInformation[] = []
for (const item of response.body) {

View file

@ -117,6 +117,7 @@ export default class LanguageProvider {
client,
typingsStatus,
this.fileConfigurationManager,
this.bufferSyncSupport,
this.description.id
),
CompletionItemProvider.triggerCharacters

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocumentWillSaveEvent, workspace } from 'coc.nvim'
import { TextDocument, TextEdit, WorkspaceEdit } from 'vscode-languageserver-protocol'
import { TextDocument, TextEdit, WorkspaceEdit, CancellationToken } from 'vscode-languageserver-protocol'
import { Command } from './commands'
import Proto from './protocol'
import TypeScriptServiceClientHost from './typescriptServiceClientHost'
@ -45,8 +45,8 @@ export default class OrganizeImportsCommand implements Command {
}
}
}
const response = await client.execute('organizeImports', args)
if (!response || !response.success) {
const response = await client.execute('organizeImports', args, CancellationToken.None)
if (!response || response.type != 'response' || !response.success) {
return
}

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.
*--------------------------------------------------------------------------------------------*/
import * as Proto from './protocol'
export enum RequestQueueingType {
/**
* Normal request that is executed in order.
*/
Normal = 1,
/**
* Request that normal requests jump in front of in the queue.
*/
LowPriority = 2,
/**
* A fence that blocks request reordering.
*
* Fences are not reordered. Unlike a normal request, a fence will never jump in front of a low priority request
* in the request queue.
*/
Fence = 3,
}
export interface RequestItem {
readonly request: Proto.Request
readonly expectsResponse: boolean
readonly isAsync: boolean
readonly queueingType: RequestQueueingType
}
export class RequestQueue {
private readonly queue: RequestItem[] = []
private sequenceNumber = 0
public get length(): number {
return this.queue.length
}
public enqueue(item: RequestItem): void {
if (item.queueingType === RequestQueueingType.Normal) {
let index = this.queue.length - 1
while (index >= 0) {
if (this.queue[index].queueingType !== RequestQueueingType.LowPriority) {
break
}
--index
}
this.queue.splice(index + 1, 0, item)
} else {
// Only normal priority requests can be reordered. All other requests just go to the end.
this.queue.push(item)
}
}
public dequeue(): RequestItem | undefined {
return this.queue.shift()
}
public tryDeletePendingRequest(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
}
}
}

View file

@ -9,26 +9,63 @@ import API from './utils/api'
import { TypeScriptServiceConfiguration } from './utils/configuration'
import Logger from './utils/logger'
export class CancelledResponse {
public readonly type: 'cancelled' = 'cancelled'
export namespace ServerResponse {
constructor(
public readonly reason: string
) { }
export class Cancelled {
public readonly type = 'cancelled'
constructor(
public readonly reason: string
) { }
}
// tslint:disable-next-line: new-parens
export const NoContent = new class { public readonly type = 'noContent' }
export type Response<T extends Proto.Response> = T | Cancelled | typeof NoContent
}
export class NoContentResponse {
public readonly type: 'noContent' = 'noContent'
}
export type ServerResponse<T extends Proto.Response> = T | CancelledResponse | NoContentResponse
export interface TypeScriptServerPlugin {
readonly path: string
readonly name: string
readonly languages: string[]
}
export interface TypeScriptRequestTypes {
'applyCodeActionCommand': [Proto.ApplyCodeActionCommandRequestArgs, Proto.ApplyCodeActionCommandResponse]
'completionEntryDetails': [Proto.CompletionDetailsRequestArgs, Proto.CompletionDetailsResponse]
'completionInfo': [Proto.CompletionsRequestArgs, Proto.CompletionInfoResponse]
// tslint:disable-next-line: deprecation
'completions': [Proto.CompletionsRequestArgs, Proto.CompletionsResponse]
'configure': [Proto.ConfigureRequestArguments, Proto.ConfigureResponse]
'definition': [Proto.FileLocationRequestArgs, Proto.DefinitionResponse]
'definitionAndBoundSpan': [Proto.FileLocationRequestArgs, Proto.DefinitionInfoAndBoundSpanReponse]
'docCommentTemplate': [Proto.FileLocationRequestArgs, Proto.DocCommandTemplateResponse]
'documentHighlights': [Proto.DocumentHighlightsRequestArgs, Proto.DocumentHighlightsResponse]
'format': [Proto.FormatRequestArgs, Proto.FormatResponse]
'formatonkey': [Proto.FormatOnKeyRequestArgs, Proto.FormatResponse]
'getApplicableRefactors': [Proto.GetApplicableRefactorsRequestArgs, Proto.GetApplicableRefactorsResponse]
'getCodeFixes': [Proto.CodeFixRequestArgs, Proto.CodeFixResponse]
'getCombinedCodeFix': [Proto.GetCombinedCodeFixRequestArgs, Proto.GetCombinedCodeFixResponse]
'getEditsForFileRename': [Proto.GetEditsForFileRenameRequestArgs, Proto.GetEditsForFileRenameResponse]
'getEditsForRefactor': [Proto.GetEditsForRefactorRequestArgs, Proto.GetEditsForRefactorResponse]
'getOutliningSpans': [Proto.FileRequestArgs, Proto.OutliningSpansResponse]
'getSupportedCodeFixes': [null, Proto.GetSupportedCodeFixesResponse]
'implementation': [Proto.FileLocationRequestArgs, Proto.ImplementationResponse]
'jsxClosingTag': [Proto.JsxClosingTagRequestArgs, Proto.JsxClosingTagResponse]
'navto': [Proto.NavtoRequestArgs, Proto.NavtoResponse]
'navtree': [Proto.FileRequestArgs, Proto.NavTreeResponse]
// tslint:disable-next-line: deprecation
'occurrences': [Proto.FileLocationRequestArgs, Proto.OccurrencesResponse]
'organizeImports': [Proto.OrganizeImportsRequestArgs, Proto.OrganizeImportsResponse]
'projectInfo': [Proto.ProjectInfoRequestArgs, Proto.ProjectInfoResponse]
'quickinfo': [Proto.FileLocationRequestArgs, Proto.QuickInfoResponse]
'references': [Proto.FileLocationRequestArgs, Proto.ReferencesResponse]
'rename': [Proto.RenameRequestArgs, Proto.RenameResponse]
'signatureHelp': [Proto.SignatureHelpRequestArgs, Proto.SignatureHelpResponse]
'typeDefinition': [Proto.FileLocationRequestArgs, Proto.TypeDefinitionResponse]
}
export interface ITypeScriptServiceClient {
apiVersion: API
configuration: TypeScriptServiceConfiguration
@ -45,191 +82,20 @@ export interface ITypeScriptServiceClient {
toPath(uri: string): string
toResource(path: string): string
execute(
command: 'configure',
args: Proto.ConfigureRequestArguments,
token?: CancellationToken
): Promise<Proto.ConfigureResponse>
execute(
command: 'configurePlugin',
args: Proto.ConfigurePluginRequestArguments,
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>
execute<K extends keyof TypeScriptRequestTypes>(
command: K,
args: TypeScriptRequestTypes[K][0],
token: CancellationToken,
lowPriority?: boolean
): Promise<ServerResponse.Response<TypeScriptRequestTypes[K][1]>>
executeWithoutWaitingForResponse(command: 'open', args: Proto.OpenRequestArgs): void
executeWithoutWaitingForResponse(command: 'close', args: Proto.FileRequestArgs): void
executeWithoutWaitingForResponse(command: 'change', args: Proto.ChangeRequestArgs): void
// executeWithoutWaitingForResponse(command: 'updateOpen', args: Proto.UpdateOpenRequestArgs): void
executeWithoutWaitingForResponse(command: 'compilerOptionsForInferredProjects', args: Proto.SetCompilerOptionsForInferredProjectsArgs): void
executeWithoutWaitingForResponse(command: 'reloadProjects', args: null): void
executeWithoutWaitingForResponse(command: 'configurePlugin', args: Proto.ConfigurePluginRequestArguments): void
executeAsync(command: 'geterr', args: Proto.GeterrRequestArgs, token: CancellationToken): Promise<ServerResponse.Response<Proto.Response>>
}

View file

@ -13,7 +13,7 @@ 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 { ITypeScriptServiceClient, ServerResponse } from './typescriptService'
import API from './utils/api'
import { TsServerLogLevel, TypeScriptServiceConfiguration } from './utils/configuration'
import Logger from './utils/logger'
@ -25,83 +25,8 @@ import { TypeScriptVersion, TypeScriptVersionProvider } from './utils/versionPro
import VersionStatus from './utils/versionStatus'
import { PluginManager } from '../utils/plugins'
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
}
}
}
import { CallbackMap } from './callbackMap'
import { RequestItem, RequestQueue, RequestQueueingType } from './requestQueue'
class ForkedTsServerProcess {
constructor(private childProcess: cp.ChildProcess) { }
@ -154,8 +79,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
private lastStart: number
private numberRestarts: number
private cancellationPipeName: string | null = null
private requestQueue: RequestQueue
private callbacks: CallbackMap
private _callbacks = new CallbackMap<Proto.Response>()
private _requestQueue = new RequestQueue()
private _pendingResponses = new Set<number>()
private versionStatus: VersionStatus
private readonly _onTsServerStarted = new Emitter<API>()
private readonly _onProjectLanguageServiceStateChanged = new Emitter<Proto.ProjectLanguageServiceStateEventBody>()
@ -174,8 +101,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
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
@ -328,8 +253,6 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
}
this._apiVersion = currentVersion.version
this.versionStatus.onDidChangeTypeScriptVersion(currentVersion)
this.requestQueue = new RequestQueue()
this.callbacks = new CallbackMap()
this.lastError = null
const tsServerForkArgs = await this.getTsServerArgs()
const debugPort = this._configuration.debugPort
@ -436,7 +359,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
const configureOptions: Proto.ConfigureRequestArguments = {
hostInfo: 'nvim-coc'
}
this.execute('configure', configureOptions) // tslint:disable-line
this.execute('configure', configureOptions, CancellationToken.None) // tslint:disable-line
}
this.setCompilerOptionsForInferredProjects(this._configuration)
if (resendModels) {
@ -451,7 +374,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
const args: Proto.SetCompilerOptionsForInferredProjectsArgs = {
options: this.getCompilerOptionsForInferredProjects(configuration)
}
this.execute('compilerOptionsForInferredProjects', args, true) // tslint:disable-line
this.executeWithoutWaitingForResponse('compilerOptionsForInferredProjects', args) // tslint:disable-line
}
private getCompilerOptionsForInferredProjects(
@ -469,8 +392,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
this.state = ServiceStat.Stopped
this.servicePromise = null
this.tsServerLogFile = null
this.callbacks.destroy(new Error('Service died.'))
this.callbacks = new CallbackMap()
this._callbacks.destroy('Service died.')
this._callbacks = new CallbackMap<Proto.Response>()
this._requestQueue = new RequestQueue()
this._pendingResponses = new Set<number>()
if (restart) {
const diff = Date.now() - this.lastStart
this.numberRestarts++
@ -558,58 +483,72 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
}
public execute(
command: string,
args: any,
expectsResultOrToken?: boolean | CancellationToken
): Promise<any> {
command: string, args: any,
token: CancellationToken,
lowPriority?: boolean): Promise<ServerResponse.Response<Proto.Response>> {
return this.executeImpl(command, args, {
isAsync: false,
token,
expectsResult: true,
lowPriority
})
}
public executeAsync(
command: string, args: Proto.GeterrRequestArgs,
token: CancellationToken): Promise<ServerResponse.Response<Proto.Response>> {
return this.executeImpl(command, args, {
isAsync: true,
token,
expectsResult: true
})
}
public executeWithoutWaitingForResponse(command: string, args: any): void {
this.executeImpl(command, args, {
isAsync: false,
token: undefined,
expectsResult: false
})
}
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: false, lowPriority?: boolean }): undefined
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>>
private executeImpl(command: string, args: any, executeInfo: { isAsync: boolean, token?: CancellationToken, expectsResult: boolean, lowPriority?: boolean }): Promise<ServerResponse.Response<Proto.Response>> | undefined {
if (this.servicePromise == null) {
return Promise.resolve()
}
let token: CancellationToken | undefined
let expectsResult = true
if (typeof expectsResultOrToken === 'boolean') {
expectsResult = expectsResultOrToken
} else {
token = expectsResultOrToken
return
}
const request = this.requestQueue.createRequest(command, args)
const request = this._requestQueue.createRequest(command, args)
const requestInfo: RequestItem = {
request,
callbacks: null
expectsResponse: executeInfo.expectsResult,
isAsync: executeInfo.isAsync,
queueingType: getQueueingType(command, executeInfo.lowPriority)
}
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)
let result: Promise<ServerResponse.Response<Proto.Response>> | undefined
if (executeInfo.expectsResult) {
result = new Promise<ServerResponse.Response<Proto.Response>>((resolve, reject) => {
this._callbacks.add(request.seq, { onSuccess: resolve, onError: reject, startTime: Date.now(), isAsync: executeInfo.isAsync }, executeInfo.isAsync)
if (executeInfo.token) {
executeInfo.token.onCancellationRequested(() => {
this.tryCancelRequest(request.seq, command)
})
}
}).catch((err: any) => {
if (!wasCancelled && command != 'signatureHelp') {
this.error(`'${command}' request failed with error.`, err)
}
}).catch((err: Error) => {
throw err
})
} else {
result = Promise.resolve(null)
}
this.requestQueue.push(requestInfo)
this.sendNextRequests()
this._requestQueue.enqueue(requestInfo)
this.sendNextRequests()
return result
}
private sendNextRequests(): void {
while (
this.callbacks.pendingResponses === 0 &&
this.requestQueue.length > 0
) {
const item = this.requestQueue.shift()
while (this._pendingResponses.size === 0 && this._requestQueue.length > 0) {
const item = this._requestQueue.dequeue()
if (item) {
this.sendRequest(item)
}
@ -618,35 +557,32 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
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.tracer.traceRequest(serverRequest, requestItem.expectsResponse, this._requestQueue.length)
if (requestItem.expectsResponse && !requestItem.isAsync) {
this._pendingResponses.add(requestItem.request.seq)
}
this.service()
.then(childProcess => {
this.service().then(childProcess => {
try {
childProcess.write(serverRequest)
})
.then(undefined, err => {
const callback = this.callbacks.fetch(serverRequest.seq)
} catch (err) {
const callback = this.fetchCallback(serverRequest.seq)
if (callback) {
callback.e(err)
callback.onError(err)
}
})
}
})
}
private tryCancelRequest(seq: number): boolean {
private tryCancelRequest(seq: number, command: string): boolean {
try {
if (this.requestQueue.tryCancelPendingRequest(seq)) {
this.tracer.logTrace(`TypeScript Service: canceled request with sequence number ${seq}`)
if (this._requestQueue.tryDeletePendingRequest(seq)) {
this.tracer.logTrace(`TypeScript Server: 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}`)
if (this.cancellationPipeName) {
this.tracer.logTrace(`TypeScript Server: trying to cancel ongoing request with sequence number ${seq}`)
try {
fs.writeFileSync(this.cancellationPipeName + seq, '')
} catch {
@ -655,43 +591,73 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
return true
}
this.tracer.logTrace(
`TypeScript Service: tried to cancel request with sequence number ${seq}. But request got already delivered.`
)
this.tracer.logTrace(`TypeScript Server: 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}`))
const callback = this.fetchCallback(seq)
if (callback) {
callback.onSuccess(new ServerResponse.Cancelled(`Cancelled request ${seq} - ${command}`))
}
}
}
private fetchCallback(seq: number): any {
const callback = this._callbacks.fetch(seq)
if (!callback) {
return undefined
}
this._pendingResponses.delete(seq)
return callback
}
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)
switch (message.type) {
case 'response':
this.dispatchResponse(message as Proto.Response)
break
case 'event':
const event = message as Proto.Event
if (event.event === 'requestCompleted') {
const seq = (event as Proto.RequestCompletedEvent).body.request_seq
const p = this._callbacks.fetch(seq)
if (p) {
this.tracer.traceRequestCompleted('requestCompleted', seq, p.startTime)
p.onSuccess(undefined)
}
} else {
p.e(response)
this.tracer.traceEvent(event)
this.dispatchEvent(event)
}
}
} 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')
break
default:
throw new Error(`Unknown message type ${message.type} received`)
}
} finally {
this.sendNextRequests()
}
}
private dispatchResponse(response: Proto.Response): void {
const callback = this.fetchCallback(response.request_seq)
if (!callback) {
return
}
this.tracer.traceResponse(response, callback.startTime)
if (response.success) {
callback.onSuccess(response)
} else if (response.message === 'No content available.') {
// Special case where response itself is successful but there is not any data to return.
callback.onSuccess(ServerResponse.NoContent)
} else {
callback.onError(new Error(response.message))
}
}
private dispatchEvent(event: Proto.Event): void {
switch (event.event) {
case 'syntaxDiag':
@ -838,7 +804,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
if (!this.servicePromise) return
this.servicePromise.then(() => {
// tslint:disable-next-line: no-floating-promises
this.execute('configurePlugin', { pluginName, configuration }, false)
this.executeWithoutWaitingForResponse('configurePlugin', { pluginName, configuration })
})
}
}
@ -855,3 +821,15 @@ function getDiagnosticsKind(event: Proto.Event): DiagnosticKind {
}
throw new Error('Unknown dignostics kind')
}
const fenceCommands = new Set(['change', 'close', 'open'])
function getQueueingType(
command: string,
lowPriority?: boolean
): RequestQueueingType {
if (fenceCommands.has(command)) {
return RequestQueueingType.Fence
}
return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DiagnosticKind, disposeAll, workspace } from 'coc.nvim'
import { Range, Diagnostic, DiagnosticSeverity, Disposable, Position } from 'vscode-languageserver-protocol'
import { Range, Diagnostic, DiagnosticSeverity, Disposable, Position, CancellationToken } from 'vscode-languageserver-protocol'
import Uri from 'vscode-uri'
import LanguageProvider from './languageProvider'
import * as Proto from './protocol'
@ -113,7 +113,7 @@ export default class TypeScriptServiceClientHost implements Disposable {
}
public reloadProjects(): void {
this.client.execute('reloadProjects', null, false) // tslint:disable-line
this.client.execute('reloadProjects', null, CancellationToken.None)
this.triggerAllDiagnostics()
}

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { WorkspaceEdit } from 'vscode-languageserver-protocol'
import { WorkspaceEdit, CancellationToken } from 'vscode-languageserver-protocol'
import { workspace } from 'coc.nvim'
import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
@ -37,8 +37,8 @@ export async function applyCodeActionCommands(
// 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) {
const response = await client.execute('applyCodeActionCommand', { command }, CancellationToken.None)
if (!response || response.type != 'response' || !response.body) {
return false
}
}

View file

@ -98,4 +98,11 @@ export default class Tracer {
this.logger.logLevel('Trace', message, data)
}
}
public traceRequestCompleted(command: string, request_seq: number, startTime: number): any {
if (this.trace === Trace.Off) {
return
}
this.logTrace(`Async response received: ${command} (${request_seq}). Request took ${Date.now() - startTime} ms.`)
}
}