UNPKG

typescript-language-server

Version:

Language Server Protocol (LSP) implementation for TypeScript using tsserver

208 lines 8.31 kB
/*--------------------------------------------------------------------------------------------- * 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