rework of typescriptService, support interuptGetErr
This commit is contained in:
parent
77690fb036
commit
cbb489c462
31 changed files with 523 additions and 443 deletions
src/server
callbackMap.tscommands.ts
features
baseCodeLensProvider.tsbufferSyncSupport.tscompletionItemProvider.tsdefinitionProvider.tsdocumentHighlight.tsdocumentSymbol.tsfileConfigurationManager.tsfolding.tsformatting.tshover.tsimplementationsCodeLens.tsprojectError.tsquickfix.tsrefactor.tsreferences.tsreferencesCodeLens.tsrename.tssignatureHelp.tstagCompletion.tsupdatePathOnRename.tsworkspaceSymbols.ts
languageProvider.tsorganizeImports.tsrequestQueue.tstypescriptService.tstypescriptServiceClient.tstypescriptServiceClientHost.tsutils
51
src/server/callbackMap.ts
Normal file
51
src/server/callbackMap.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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[] = []
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -117,6 +117,7 @@ export default class LanguageProvider {
|
|||
client,
|
||||
typingsStatus,
|
||||
this.fileConfigurationManager,
|
||||
this.bufferSyncSupport,
|
||||
this.description.id
|
||||
),
|
||||
CompletionItemProvider.triggerCharacters
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
81
src/server/requestQueue.ts
Normal file
81
src/server/requestQueue.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>>
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.`)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue