typescript-language-server
Version:
Language Server Protocol (LSP) implementation for TypeScript using tsserver
208 lines • 8.31 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
* Copyright (C) 2022 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/
import { RequestQueue, RequestQueueingType } from './requestQueue.js';
import { ServerResponse } from './requests.js';
import { CallbackMap } from './callbackMap.js';
import { TypeScriptServerError } from './serverError.js';
export var ExecutionTarget;
(function (ExecutionTarget) {
ExecutionTarget[ExecutionTarget["Semantic"] = 0] = "Semantic";
ExecutionTarget[ExecutionTarget["Syntax"] = 1] = "Syntax";
})(ExecutionTarget = ExecutionTarget || (ExecutionTarget = {}));
export class ProcessBasedTsServer {
constructor(_serverId, _serverSource, _process, _tsServerLogFile, _requestCanceller, _version) {
this._serverId = _serverId;
this._serverSource = _serverSource;
this._process = _process;
this._tsServerLogFile = _tsServerLogFile;
this._requestCanceller = _requestCanceller;
this._version = _version;
this._requestQueue = new RequestQueue();
this._callbacks = new CallbackMap();
this._pendingResponses = new Set();
this._eventHandlers = new Set();
this._exitHandlers = new Set();
this._errorHandlers = new Set();
this._process.onData(msg => {
this.dispatchMessage(msg);
});
this._process.onExit((code, signal) => {
this._exitHandlers.forEach(handler => handler({ code, signal }));
this._callbacks.destroy('server exited');
});
this._process.onError(error => {
this._errorHandlers.forEach(handler => handler(error));
this._callbacks.destroy('server errored');
});
}
onEvent(handler) {
this._eventHandlers.add(handler);
}
onExit(handler) {
this._exitHandlers.add(handler);
}
onError(handler) {
this._errorHandlers.add(handler);
}
get tsServerLogFile() {
return this._tsServerLogFile;
}
write(serverRequest) {
this._process.write(serverRequest);
}
dispose() {
this._callbacks.destroy('server disposed');
this._pendingResponses.clear();
this._eventHandlers.clear();
this._exitHandlers.clear();
this._errorHandlers.clear();
}
kill() {
this.dispose();
this._process.kill();
}
dispatchMessage(message) {
try {
switch (message.type) {
case 'response':
if (this._serverSource) {
this.dispatchResponse({
...message,
});
}
else {
this.dispatchResponse(message);
}
break;
case 'event': {
const event = message;
if (event.event === 'requestCompleted') {
const seq = event.body.request_seq;
const callback = this._callbacks.fetch(seq);
if (callback) {
// this._tracer.traceRequestCompleted(this._serverId, 'requestCompleted', seq, callback);
callback.onSuccess(undefined);
}
}
else {
// this._tracer.traceEvent(this._serverId, event);
this._eventHandlers.forEach(handler => handler(event));
}
break;
}
default:
throw new Error(`Unknown message type ${message.type} received`);
}
}
finally {
this.sendNextRequests();
}
}
tryCancelRequest(seq, command) {
try {
if (this._requestQueue.tryDeletePendingRequest(seq)) {
this.logTrace(`Canceled request with sequence number ${seq}`);
return true;
}
if (this._requestCanceller.tryCancelOngoingRequest(seq)) {
return true;
}
this.logTrace(`Tried to cancel request with sequence number ${seq}. But request got already delivered.`);
return false;
}
finally {
const callback = this.fetchCallback(seq);
callback?.onSuccess(new ServerResponse.Cancelled(`Cancelled request ${seq} - ${command}`));
}
}
dispatchResponse(response) {
const callback = this.fetchCallback(response.request_seq);
if (!callback) {
return;
}
// this._tracer.traceResponse(this._serverId, response, callback);
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(TypeScriptServerError.create(this._serverId, this._version, response));
}
}
executeImpl(command, args, executeInfo) {
const request = this._requestQueue.createRequest(command, args);
const requestInfo = {
request,
expectsResponse: executeInfo.expectsResult,
isAsync: executeInfo.isAsync,
queueingType: ProcessBasedTsServer.getQueueingType(command, executeInfo.lowPriority),
};
let result;
if (executeInfo.expectsResult) {
result = new Promise((resolve, reject) => {
this._callbacks.add(request.seq, { onSuccess: resolve, onError: reject, queuingStartTime: Date.now(), isAsync: executeInfo.isAsync }, executeInfo.isAsync);
if (executeInfo.token) {
executeInfo.token.onCancellationRequested(() => {
this.tryCancelRequest(request.seq, command);
});
}
});
}
this._requestQueue.enqueue(requestInfo);
this.sendNextRequests();
return [result];
}
sendNextRequests() {
// console.error({ pending: this._pendingResponses.size, queue: this._requestQueue.length });
while (this._pendingResponses.size === 0 && this._requestQueue.length > 0) {
const item = this._requestQueue.dequeue();
if (item) {
this.sendRequest(item);
}
}
}
sendRequest(requestItem) {
const serverRequest = requestItem.request;
// this._tracer.traceRequest(this._serverId, serverRequest, requestItem.expectsResponse, this._requestQueue.length);
if (requestItem.expectsResponse && !requestItem.isAsync) {
this._pendingResponses.add(requestItem.request.seq);
}
try {
this.write(serverRequest);
}
catch (err) {
const callback = this.fetchCallback(serverRequest.seq);
callback?.onError(err);
}
}
fetchCallback(seq) {
const callback = this._callbacks.fetch(seq);
if (!callback) {
return undefined;
}
this._pendingResponses.delete(seq);
return callback;
}
logTrace(_message) {
// this._tracer.logTrace(this._serverId, message);
}
static getQueueingType(command, lowPriority) {
if (ProcessBasedTsServer.fenceCommands.has(command)) {
return RequestQueueingType.Fence;
}
return lowPriority ? RequestQueueingType.LowPriority : RequestQueueingType.Normal;
}
}
ProcessBasedTsServer.fenceCommands = new Set(['change', 'close', 'open', 'updateOpen']);
//# sourceMappingURL=server.js.map