924 lines
36 KiB
JavaScript
Executable File
924 lines
36 KiB
JavaScript
Executable File
/* --------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
* ------------------------------------------------------------------------------------------ */
|
|
/// <reference path="./thenable.ts" />
|
|
'use strict';
|
|
function __export(m) {
|
|
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
|
}
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
const Is = require("./is");
|
|
const messages_1 = require("./messages");
|
|
exports.RequestType = messages_1.RequestType;
|
|
exports.RequestType0 = messages_1.RequestType0;
|
|
exports.RequestType1 = messages_1.RequestType1;
|
|
exports.RequestType2 = messages_1.RequestType2;
|
|
exports.RequestType3 = messages_1.RequestType3;
|
|
exports.RequestType4 = messages_1.RequestType4;
|
|
exports.RequestType5 = messages_1.RequestType5;
|
|
exports.RequestType6 = messages_1.RequestType6;
|
|
exports.RequestType7 = messages_1.RequestType7;
|
|
exports.RequestType8 = messages_1.RequestType8;
|
|
exports.RequestType9 = messages_1.RequestType9;
|
|
exports.ResponseError = messages_1.ResponseError;
|
|
exports.ErrorCodes = messages_1.ErrorCodes;
|
|
exports.NotificationType = messages_1.NotificationType;
|
|
exports.NotificationType0 = messages_1.NotificationType0;
|
|
exports.NotificationType1 = messages_1.NotificationType1;
|
|
exports.NotificationType2 = messages_1.NotificationType2;
|
|
exports.NotificationType3 = messages_1.NotificationType3;
|
|
exports.NotificationType4 = messages_1.NotificationType4;
|
|
exports.NotificationType5 = messages_1.NotificationType5;
|
|
exports.NotificationType6 = messages_1.NotificationType6;
|
|
exports.NotificationType7 = messages_1.NotificationType7;
|
|
exports.NotificationType8 = messages_1.NotificationType8;
|
|
exports.NotificationType9 = messages_1.NotificationType9;
|
|
const messageReader_1 = require("./messageReader");
|
|
exports.MessageReader = messageReader_1.MessageReader;
|
|
exports.StreamMessageReader = messageReader_1.StreamMessageReader;
|
|
exports.IPCMessageReader = messageReader_1.IPCMessageReader;
|
|
exports.SocketMessageReader = messageReader_1.SocketMessageReader;
|
|
const messageWriter_1 = require("./messageWriter");
|
|
exports.MessageWriter = messageWriter_1.MessageWriter;
|
|
exports.StreamMessageWriter = messageWriter_1.StreamMessageWriter;
|
|
exports.IPCMessageWriter = messageWriter_1.IPCMessageWriter;
|
|
exports.SocketMessageWriter = messageWriter_1.SocketMessageWriter;
|
|
const events_1 = require("./events");
|
|
exports.Disposable = events_1.Disposable;
|
|
exports.Event = events_1.Event;
|
|
exports.Emitter = events_1.Emitter;
|
|
const cancellation_1 = require("./cancellation");
|
|
exports.CancellationTokenSource = cancellation_1.CancellationTokenSource;
|
|
exports.CancellationToken = cancellation_1.CancellationToken;
|
|
const linkedMap_1 = require("./linkedMap");
|
|
__export(require("./pipeSupport"));
|
|
__export(require("./socketSupport"));
|
|
var CancelNotification;
|
|
(function (CancelNotification) {
|
|
CancelNotification.type = new messages_1.NotificationType('$/cancelRequest');
|
|
})(CancelNotification || (CancelNotification = {}));
|
|
exports.NullLogger = Object.freeze({
|
|
error: () => { },
|
|
warn: () => { },
|
|
info: () => { },
|
|
log: () => { }
|
|
});
|
|
var Trace;
|
|
(function (Trace) {
|
|
Trace[Trace["Off"] = 0] = "Off";
|
|
Trace[Trace["Messages"] = 1] = "Messages";
|
|
Trace[Trace["Verbose"] = 2] = "Verbose";
|
|
})(Trace = exports.Trace || (exports.Trace = {}));
|
|
(function (Trace) {
|
|
function fromString(value) {
|
|
value = value.toLowerCase();
|
|
switch (value) {
|
|
case 'off':
|
|
return Trace.Off;
|
|
case 'messages':
|
|
return Trace.Messages;
|
|
case 'verbose':
|
|
return Trace.Verbose;
|
|
default:
|
|
return Trace.Off;
|
|
}
|
|
}
|
|
Trace.fromString = fromString;
|
|
function toString(value) {
|
|
switch (value) {
|
|
case Trace.Off:
|
|
return 'off';
|
|
case Trace.Messages:
|
|
return 'messages';
|
|
case Trace.Verbose:
|
|
return 'verbose';
|
|
default:
|
|
return 'off';
|
|
}
|
|
}
|
|
Trace.toString = toString;
|
|
})(Trace = exports.Trace || (exports.Trace = {}));
|
|
var TraceFormat;
|
|
(function (TraceFormat) {
|
|
TraceFormat["Text"] = "text";
|
|
TraceFormat["JSON"] = "json";
|
|
})(TraceFormat = exports.TraceFormat || (exports.TraceFormat = {}));
|
|
(function (TraceFormat) {
|
|
function fromString(value) {
|
|
value = value.toLowerCase();
|
|
if (value === 'json') {
|
|
return TraceFormat.JSON;
|
|
}
|
|
else {
|
|
return TraceFormat.Text;
|
|
}
|
|
}
|
|
TraceFormat.fromString = fromString;
|
|
})(TraceFormat = exports.TraceFormat || (exports.TraceFormat = {}));
|
|
var SetTraceNotification;
|
|
(function (SetTraceNotification) {
|
|
SetTraceNotification.type = new messages_1.NotificationType('$/setTraceNotification');
|
|
})(SetTraceNotification = exports.SetTraceNotification || (exports.SetTraceNotification = {}));
|
|
var LogTraceNotification;
|
|
(function (LogTraceNotification) {
|
|
LogTraceNotification.type = new messages_1.NotificationType('$/logTraceNotification');
|
|
})(LogTraceNotification = exports.LogTraceNotification || (exports.LogTraceNotification = {}));
|
|
var ConnectionErrors;
|
|
(function (ConnectionErrors) {
|
|
/**
|
|
* The connection is closed.
|
|
*/
|
|
ConnectionErrors[ConnectionErrors["Closed"] = 1] = "Closed";
|
|
/**
|
|
* The connection got disposed.
|
|
*/
|
|
ConnectionErrors[ConnectionErrors["Disposed"] = 2] = "Disposed";
|
|
/**
|
|
* The connection is already in listening mode.
|
|
*/
|
|
ConnectionErrors[ConnectionErrors["AlreadyListening"] = 3] = "AlreadyListening";
|
|
})(ConnectionErrors = exports.ConnectionErrors || (exports.ConnectionErrors = {}));
|
|
class ConnectionError extends Error {
|
|
constructor(code, message) {
|
|
super(message);
|
|
this.code = code;
|
|
Object.setPrototypeOf(this, ConnectionError.prototype);
|
|
}
|
|
}
|
|
exports.ConnectionError = ConnectionError;
|
|
var ConnectionStrategy;
|
|
(function (ConnectionStrategy) {
|
|
function is(value) {
|
|
let candidate = value;
|
|
return candidate && Is.func(candidate.cancelUndispatched);
|
|
}
|
|
ConnectionStrategy.is = is;
|
|
})(ConnectionStrategy = exports.ConnectionStrategy || (exports.ConnectionStrategy = {}));
|
|
var ConnectionState;
|
|
(function (ConnectionState) {
|
|
ConnectionState[ConnectionState["New"] = 1] = "New";
|
|
ConnectionState[ConnectionState["Listening"] = 2] = "Listening";
|
|
ConnectionState[ConnectionState["Closed"] = 3] = "Closed";
|
|
ConnectionState[ConnectionState["Disposed"] = 4] = "Disposed";
|
|
})(ConnectionState || (ConnectionState = {}));
|
|
function _createMessageConnection(messageReader, messageWriter, logger, strategy) {
|
|
let sequenceNumber = 0;
|
|
let notificationSquenceNumber = 0;
|
|
let unknownResponseSquenceNumber = 0;
|
|
const version = '2.0';
|
|
let starRequestHandler = undefined;
|
|
let requestHandlers = Object.create(null);
|
|
let starNotificationHandler = undefined;
|
|
let notificationHandlers = Object.create(null);
|
|
let timer;
|
|
let messageQueue = new linkedMap_1.LinkedMap();
|
|
let responsePromises = Object.create(null);
|
|
let requestTokens = Object.create(null);
|
|
let trace = Trace.Off;
|
|
let traceFormat = TraceFormat.Text;
|
|
let tracer;
|
|
let state = ConnectionState.New;
|
|
let errorEmitter = new events_1.Emitter();
|
|
let closeEmitter = new events_1.Emitter();
|
|
let unhandledNotificationEmitter = new events_1.Emitter();
|
|
let disposeEmitter = new events_1.Emitter();
|
|
function createRequestQueueKey(id) {
|
|
return 'req-' + id.toString();
|
|
}
|
|
function createResponseQueueKey(id) {
|
|
if (id === null) {
|
|
return 'res-unknown-' + (++unknownResponseSquenceNumber).toString();
|
|
}
|
|
else {
|
|
return 'res-' + id.toString();
|
|
}
|
|
}
|
|
function createNotificationQueueKey() {
|
|
return 'not-' + (++notificationSquenceNumber).toString();
|
|
}
|
|
function addMessageToQueue(queue, message) {
|
|
if (messages_1.isRequestMessage(message)) {
|
|
queue.set(createRequestQueueKey(message.id), message);
|
|
}
|
|
else if (messages_1.isResponseMessage(message)) {
|
|
queue.set(createResponseQueueKey(message.id), message);
|
|
}
|
|
else {
|
|
queue.set(createNotificationQueueKey(), message);
|
|
}
|
|
}
|
|
function cancelUndispatched(_message) {
|
|
return undefined;
|
|
}
|
|
function isListening() {
|
|
return state === ConnectionState.Listening;
|
|
}
|
|
function isClosed() {
|
|
return state === ConnectionState.Closed;
|
|
}
|
|
function isDisposed() {
|
|
return state === ConnectionState.Disposed;
|
|
}
|
|
function closeHandler() {
|
|
if (state === ConnectionState.New || state === ConnectionState.Listening) {
|
|
state = ConnectionState.Closed;
|
|
closeEmitter.fire(undefined);
|
|
}
|
|
// If the connection is disposed don't sent close events.
|
|
}
|
|
;
|
|
function readErrorHandler(error) {
|
|
errorEmitter.fire([error, undefined, undefined]);
|
|
}
|
|
function writeErrorHandler(data) {
|
|
errorEmitter.fire(data);
|
|
}
|
|
messageReader.onClose(closeHandler);
|
|
messageReader.onError(readErrorHandler);
|
|
messageWriter.onClose(closeHandler);
|
|
messageWriter.onError(writeErrorHandler);
|
|
function triggerMessageQueue() {
|
|
if (timer || messageQueue.size === 0) {
|
|
return;
|
|
}
|
|
timer = setImmediate(() => {
|
|
timer = undefined;
|
|
processMessageQueue();
|
|
});
|
|
}
|
|
function processMessageQueue() {
|
|
if (messageQueue.size === 0) {
|
|
return;
|
|
}
|
|
let message = messageQueue.shift();
|
|
try {
|
|
if (messages_1.isRequestMessage(message)) {
|
|
handleRequest(message);
|
|
}
|
|
else if (messages_1.isNotificationMessage(message)) {
|
|
handleNotification(message);
|
|
}
|
|
else if (messages_1.isResponseMessage(message)) {
|
|
handleResponse(message);
|
|
}
|
|
else {
|
|
handleInvalidMessage(message);
|
|
}
|
|
}
|
|
finally {
|
|
triggerMessageQueue();
|
|
}
|
|
}
|
|
let callback = (message) => {
|
|
try {
|
|
// We have received a cancellation message. Check if the message is still in the queue
|
|
// and cancel it if allowed to do so.
|
|
if (messages_1.isNotificationMessage(message) && message.method === CancelNotification.type.method) {
|
|
let key = createRequestQueueKey(message.params.id);
|
|
let toCancel = messageQueue.get(key);
|
|
if (messages_1.isRequestMessage(toCancel)) {
|
|
let response = strategy && strategy.cancelUndispatched ? strategy.cancelUndispatched(toCancel, cancelUndispatched) : cancelUndispatched(toCancel);
|
|
if (response && (response.error !== void 0 || response.result !== void 0)) {
|
|
messageQueue.delete(key);
|
|
response.id = toCancel.id;
|
|
traceSendingResponse(response, message.method, Date.now());
|
|
messageWriter.write(response);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
addMessageToQueue(messageQueue, message);
|
|
}
|
|
finally {
|
|
triggerMessageQueue();
|
|
}
|
|
};
|
|
function handleRequest(requestMessage) {
|
|
if (isDisposed()) {
|
|
// we return here silently since we fired an event when the
|
|
// connection got disposed.
|
|
return;
|
|
}
|
|
function reply(resultOrError, method, startTime) {
|
|
let message = {
|
|
jsonrpc: version,
|
|
id: requestMessage.id
|
|
};
|
|
if (resultOrError instanceof messages_1.ResponseError) {
|
|
message.error = resultOrError.toJson();
|
|
}
|
|
else {
|
|
message.result = resultOrError === void 0 ? null : resultOrError;
|
|
}
|
|
traceSendingResponse(message, method, startTime);
|
|
messageWriter.write(message);
|
|
}
|
|
function replyError(error, method, startTime) {
|
|
let message = {
|
|
jsonrpc: version,
|
|
id: requestMessage.id,
|
|
error: error.toJson()
|
|
};
|
|
traceSendingResponse(message, method, startTime);
|
|
messageWriter.write(message);
|
|
}
|
|
function replySuccess(result, method, startTime) {
|
|
// The JSON RPC defines that a response must either have a result or an error
|
|
// So we can't treat undefined as a valid response result.
|
|
if (result === void 0) {
|
|
result = null;
|
|
}
|
|
let message = {
|
|
jsonrpc: version,
|
|
id: requestMessage.id,
|
|
result: result
|
|
};
|
|
traceSendingResponse(message, method, startTime);
|
|
messageWriter.write(message);
|
|
}
|
|
traceReceivedRequest(requestMessage);
|
|
let element = requestHandlers[requestMessage.method];
|
|
let type;
|
|
let requestHandler;
|
|
if (element) {
|
|
type = element.type;
|
|
requestHandler = element.handler;
|
|
}
|
|
let startTime = Date.now();
|
|
if (requestHandler || starRequestHandler) {
|
|
let cancellationSource = new cancellation_1.CancellationTokenSource();
|
|
let tokenKey = String(requestMessage.id);
|
|
requestTokens[tokenKey] = cancellationSource;
|
|
try {
|
|
let handlerResult;
|
|
if (requestMessage.params === void 0 || (type !== void 0 && type.numberOfParams === 0)) {
|
|
handlerResult = requestHandler
|
|
? requestHandler(cancellationSource.token)
|
|
: starRequestHandler(requestMessage.method, cancellationSource.token);
|
|
}
|
|
else if (Is.array(requestMessage.params) && (type === void 0 || type.numberOfParams > 1)) {
|
|
handlerResult = requestHandler
|
|
? requestHandler(...requestMessage.params, cancellationSource.token)
|
|
: starRequestHandler(requestMessage.method, ...requestMessage.params, cancellationSource.token);
|
|
}
|
|
else {
|
|
handlerResult = requestHandler
|
|
? requestHandler(requestMessage.params, cancellationSource.token)
|
|
: starRequestHandler(requestMessage.method, requestMessage.params, cancellationSource.token);
|
|
}
|
|
let promise = handlerResult;
|
|
if (!handlerResult) {
|
|
delete requestTokens[tokenKey];
|
|
replySuccess(handlerResult, requestMessage.method, startTime);
|
|
}
|
|
else if (promise.then) {
|
|
promise.then((resultOrError) => {
|
|
delete requestTokens[tokenKey];
|
|
reply(resultOrError, requestMessage.method, startTime);
|
|
}, error => {
|
|
delete requestTokens[tokenKey];
|
|
if (error instanceof messages_1.ResponseError) {
|
|
replyError(error, requestMessage.method, startTime);
|
|
}
|
|
else if (error && Is.string(error.message)) {
|
|
replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`), requestMessage.method, startTime);
|
|
}
|
|
else {
|
|
replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`), requestMessage.method, startTime);
|
|
}
|
|
});
|
|
}
|
|
else {
|
|
delete requestTokens[tokenKey];
|
|
reply(handlerResult, requestMessage.method, startTime);
|
|
}
|
|
}
|
|
catch (error) {
|
|
delete requestTokens[tokenKey];
|
|
if (error instanceof messages_1.ResponseError) {
|
|
reply(error, requestMessage.method, startTime);
|
|
}
|
|
else if (error && Is.string(error.message)) {
|
|
replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed with message: ${error.message}`), requestMessage.method, startTime);
|
|
}
|
|
else {
|
|
replyError(new messages_1.ResponseError(messages_1.ErrorCodes.InternalError, `Request ${requestMessage.method} failed unexpectedly without providing any details.`), requestMessage.method, startTime);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
replyError(new messages_1.ResponseError(messages_1.ErrorCodes.MethodNotFound, `Unhandled method ${requestMessage.method}`), requestMessage.method, startTime);
|
|
}
|
|
}
|
|
function handleResponse(responseMessage) {
|
|
if (isDisposed()) {
|
|
// See handle request.
|
|
return;
|
|
}
|
|
if (responseMessage.id === null) {
|
|
if (responseMessage.error) {
|
|
logger.error(`Received response message without id: Error is: \n${JSON.stringify(responseMessage.error, undefined, 4)}`);
|
|
}
|
|
else {
|
|
logger.error(`Received response message without id. No further error information provided.`);
|
|
}
|
|
}
|
|
else {
|
|
let key = String(responseMessage.id);
|
|
let responsePromise = responsePromises[key];
|
|
traceReceivedResponse(responseMessage, responsePromise);
|
|
if (responsePromise) {
|
|
delete responsePromises[key];
|
|
try {
|
|
if (responseMessage.error) {
|
|
let error = responseMessage.error;
|
|
responsePromise.reject(new messages_1.ResponseError(error.code, error.message, error.data));
|
|
}
|
|
else if (responseMessage.result !== void 0) {
|
|
responsePromise.resolve(responseMessage.result);
|
|
}
|
|
else {
|
|
throw new Error('Should never happen.');
|
|
}
|
|
}
|
|
catch (error) {
|
|
if (error.message) {
|
|
logger.error(`Response handler '${responsePromise.method}' failed with message: ${error.message}`);
|
|
}
|
|
else {
|
|
logger.error(`Response handler '${responsePromise.method}' failed unexpectedly.`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
function handleNotification(message) {
|
|
if (isDisposed()) {
|
|
// See handle request.
|
|
return;
|
|
}
|
|
let type = undefined;
|
|
let notificationHandler;
|
|
if (message.method === CancelNotification.type.method) {
|
|
notificationHandler = (params) => {
|
|
let id = params.id;
|
|
let source = requestTokens[String(id)];
|
|
if (source) {
|
|
source.cancel();
|
|
}
|
|
};
|
|
}
|
|
else {
|
|
let element = notificationHandlers[message.method];
|
|
if (element) {
|
|
notificationHandler = element.handler;
|
|
type = element.type;
|
|
}
|
|
}
|
|
if (notificationHandler || starNotificationHandler) {
|
|
try {
|
|
traceReceivedNotification(message);
|
|
if (message.params === void 0 || (type !== void 0 && type.numberOfParams === 0)) {
|
|
notificationHandler ? notificationHandler() : starNotificationHandler(message.method);
|
|
}
|
|
else if (Is.array(message.params) && (type === void 0 || type.numberOfParams > 1)) {
|
|
notificationHandler ? notificationHandler(...message.params) : starNotificationHandler(message.method, ...message.params);
|
|
}
|
|
else {
|
|
notificationHandler ? notificationHandler(message.params) : starNotificationHandler(message.method, message.params);
|
|
}
|
|
}
|
|
catch (error) {
|
|
if (error.message) {
|
|
logger.error(`Notification handler '${message.method}' failed with message: ${error.message}`);
|
|
}
|
|
else {
|
|
logger.error(`Notification handler '${message.method}' failed unexpectedly.`);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
unhandledNotificationEmitter.fire(message);
|
|
}
|
|
}
|
|
function handleInvalidMessage(message) {
|
|
if (!message) {
|
|
logger.error('Received empty message.');
|
|
return;
|
|
}
|
|
logger.error(`Received message which is neither a response nor a notification message:\n${JSON.stringify(message, null, 4)}`);
|
|
// Test whether we find an id to reject the promise
|
|
let responseMessage = message;
|
|
if (Is.string(responseMessage.id) || Is.number(responseMessage.id)) {
|
|
let key = String(responseMessage.id);
|
|
let responseHandler = responsePromises[key];
|
|
if (responseHandler) {
|
|
responseHandler.reject(new Error('The received response has neither a result nor an error property.'));
|
|
}
|
|
}
|
|
}
|
|
function traceSendingRequest(message) {
|
|
if (trace === Trace.Off || !tracer) {
|
|
return;
|
|
}
|
|
if (traceFormat === TraceFormat.Text) {
|
|
let data = undefined;
|
|
if (trace === Trace.Verbose && message.params) {
|
|
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
|
|
}
|
|
tracer.log(`Sending request '${message.method} - (${message.id})'.`, data);
|
|
}
|
|
else {
|
|
logLSPMessage('send-request', message);
|
|
}
|
|
}
|
|
function traceSendingNotification(message) {
|
|
if (trace === Trace.Off || !tracer) {
|
|
return;
|
|
}
|
|
if (traceFormat === TraceFormat.Text) {
|
|
let data = undefined;
|
|
if (trace === Trace.Verbose) {
|
|
if (message.params) {
|
|
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
|
|
}
|
|
else {
|
|
data = 'No parameters provided.\n\n';
|
|
}
|
|
}
|
|
tracer.log(`Sending notification '${message.method}'.`, data);
|
|
}
|
|
else {
|
|
logLSPMessage('send-notification', message);
|
|
}
|
|
}
|
|
function traceSendingResponse(message, method, startTime) {
|
|
if (trace === Trace.Off || !tracer) {
|
|
return;
|
|
}
|
|
if (traceFormat === TraceFormat.Text) {
|
|
let data = undefined;
|
|
if (trace === Trace.Verbose) {
|
|
if (message.error && message.error.data) {
|
|
data = `Error data: ${JSON.stringify(message.error.data, null, 4)}\n\n`;
|
|
}
|
|
else {
|
|
if (message.result) {
|
|
data = `Result: ${JSON.stringify(message.result, null, 4)}\n\n`;
|
|
}
|
|
else if (message.error === void 0) {
|
|
data = 'No result returned.\n\n';
|
|
}
|
|
}
|
|
}
|
|
tracer.log(`Sending response '${method} - (${message.id})'. Processing request took ${Date.now() - startTime}ms`, data);
|
|
}
|
|
else {
|
|
logLSPMessage('send-response', message);
|
|
}
|
|
}
|
|
function traceReceivedRequest(message) {
|
|
if (trace === Trace.Off || !tracer) {
|
|
return;
|
|
}
|
|
if (traceFormat === TraceFormat.Text) {
|
|
let data = undefined;
|
|
if (trace === Trace.Verbose && message.params) {
|
|
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
|
|
}
|
|
tracer.log(`Received request '${message.method} - (${message.id})'.`, data);
|
|
}
|
|
else {
|
|
logLSPMessage('receive-request', message);
|
|
}
|
|
}
|
|
function traceReceivedNotification(message) {
|
|
if (trace === Trace.Off || !tracer || message.method === LogTraceNotification.type.method) {
|
|
return;
|
|
}
|
|
if (traceFormat === TraceFormat.Text) {
|
|
let data = undefined;
|
|
if (trace === Trace.Verbose) {
|
|
if (message.params) {
|
|
data = `Params: ${JSON.stringify(message.params, null, 4)}\n\n`;
|
|
}
|
|
else {
|
|
data = 'No parameters provided.\n\n';
|
|
}
|
|
}
|
|
tracer.log(`Received notification '${message.method}'.`, data);
|
|
}
|
|
else {
|
|
logLSPMessage('receive-notification', message);
|
|
}
|
|
}
|
|
function traceReceivedResponse(message, responsePromise) {
|
|
if (trace === Trace.Off || !tracer) {
|
|
return;
|
|
}
|
|
if (traceFormat === TraceFormat.Text) {
|
|
let data = undefined;
|
|
if (trace === Trace.Verbose) {
|
|
if (message.error && message.error.data) {
|
|
data = `Error data: ${JSON.stringify(message.error.data, null, 4)}\n\n`;
|
|
}
|
|
else {
|
|
if (message.result) {
|
|
data = `Result: ${JSON.stringify(message.result, null, 4)}\n\n`;
|
|
}
|
|
else if (message.error === void 0) {
|
|
data = 'No result returned.\n\n';
|
|
}
|
|
}
|
|
}
|
|
if (responsePromise) {
|
|
let error = message.error ? ` Request failed: ${message.error.message} (${message.error.code}).` : '';
|
|
tracer.log(`Received response '${responsePromise.method} - (${message.id})' in ${Date.now() - responsePromise.timerStart}ms.${error}`, data);
|
|
}
|
|
else {
|
|
tracer.log(`Received response ${message.id} without active response promise.`, data);
|
|
}
|
|
}
|
|
else {
|
|
logLSPMessage('receive-response', message);
|
|
}
|
|
}
|
|
function logLSPMessage(type, message) {
|
|
if (!tracer || trace === Trace.Off) {
|
|
return;
|
|
}
|
|
const lspMessage = {
|
|
isLSPMessage: true,
|
|
type,
|
|
message,
|
|
timestamp: Date.now()
|
|
};
|
|
tracer.log(lspMessage);
|
|
}
|
|
function throwIfClosedOrDisposed() {
|
|
if (isClosed()) {
|
|
throw new ConnectionError(ConnectionErrors.Closed, 'Connection is closed.');
|
|
}
|
|
if (isDisposed()) {
|
|
throw new ConnectionError(ConnectionErrors.Disposed, 'Connection is disposed.');
|
|
}
|
|
}
|
|
function throwIfListening() {
|
|
if (isListening()) {
|
|
throw new ConnectionError(ConnectionErrors.AlreadyListening, 'Connection is already listening');
|
|
}
|
|
}
|
|
function throwIfNotListening() {
|
|
if (!isListening()) {
|
|
throw new Error('Call listen() first.');
|
|
}
|
|
}
|
|
function undefinedToNull(param) {
|
|
if (param === void 0) {
|
|
return null;
|
|
}
|
|
else {
|
|
return param;
|
|
}
|
|
}
|
|
function computeMessageParams(type, params) {
|
|
let result;
|
|
let numberOfParams = type.numberOfParams;
|
|
switch (numberOfParams) {
|
|
case 0:
|
|
result = null;
|
|
break;
|
|
case 1:
|
|
result = undefinedToNull(params[0]);
|
|
break;
|
|
default:
|
|
result = [];
|
|
for (let i = 0; i < params.length && i < numberOfParams; i++) {
|
|
result.push(undefinedToNull(params[i]));
|
|
}
|
|
if (params.length < numberOfParams) {
|
|
for (let i = params.length; i < numberOfParams; i++) {
|
|
result.push(null);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
let connection = {
|
|
sendNotification: (type, ...params) => {
|
|
throwIfClosedOrDisposed();
|
|
let method;
|
|
let messageParams;
|
|
if (Is.string(type)) {
|
|
method = type;
|
|
switch (params.length) {
|
|
case 0:
|
|
messageParams = null;
|
|
break;
|
|
case 1:
|
|
messageParams = params[0];
|
|
break;
|
|
default:
|
|
messageParams = params;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
method = type.method;
|
|
messageParams = computeMessageParams(type, params);
|
|
}
|
|
let notificationMessage = {
|
|
jsonrpc: version,
|
|
method: method,
|
|
params: messageParams
|
|
};
|
|
traceSendingNotification(notificationMessage);
|
|
messageWriter.write(notificationMessage);
|
|
},
|
|
onNotification: (type, handler) => {
|
|
throwIfClosedOrDisposed();
|
|
if (Is.func(type)) {
|
|
starNotificationHandler = type;
|
|
}
|
|
else if (handler) {
|
|
if (Is.string(type)) {
|
|
notificationHandlers[type] = { type: undefined, handler };
|
|
}
|
|
else {
|
|
notificationHandlers[type.method] = { type, handler };
|
|
}
|
|
}
|
|
},
|
|
sendRequest: (type, ...params) => {
|
|
throwIfClosedOrDisposed();
|
|
throwIfNotListening();
|
|
let method;
|
|
let messageParams;
|
|
let token = undefined;
|
|
if (Is.string(type)) {
|
|
method = type;
|
|
switch (params.length) {
|
|
case 0:
|
|
messageParams = null;
|
|
break;
|
|
case 1:
|
|
// The cancellation token is optional so it can also be undefined.
|
|
if (cancellation_1.CancellationToken.is(params[0])) {
|
|
messageParams = null;
|
|
token = params[0];
|
|
}
|
|
else {
|
|
messageParams = undefinedToNull(params[0]);
|
|
}
|
|
break;
|
|
default:
|
|
const last = params.length - 1;
|
|
if (cancellation_1.CancellationToken.is(params[last])) {
|
|
token = params[last];
|
|
if (params.length === 2) {
|
|
messageParams = undefinedToNull(params[0]);
|
|
}
|
|
else {
|
|
messageParams = params.slice(0, last).map(value => undefinedToNull(value));
|
|
}
|
|
}
|
|
else {
|
|
messageParams = params.map(value => undefinedToNull(value));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
method = type.method;
|
|
messageParams = computeMessageParams(type, params);
|
|
let numberOfParams = type.numberOfParams;
|
|
token = cancellation_1.CancellationToken.is(params[numberOfParams]) ? params[numberOfParams] : undefined;
|
|
}
|
|
let id = sequenceNumber++;
|
|
let result = new Promise((resolve, reject) => {
|
|
let requestMessage = {
|
|
jsonrpc: version,
|
|
id: id,
|
|
method: method,
|
|
params: messageParams
|
|
};
|
|
let responsePromise = { method: method, timerStart: Date.now(), resolve, reject };
|
|
traceSendingRequest(requestMessage);
|
|
try {
|
|
messageWriter.write(requestMessage);
|
|
}
|
|
catch (e) {
|
|
// Writing the message failed. So we need to reject the promise.
|
|
responsePromise.reject(new messages_1.ResponseError(messages_1.ErrorCodes.MessageWriteError, e.message ? e.message : 'Unknown reason'));
|
|
responsePromise = null;
|
|
}
|
|
if (responsePromise) {
|
|
responsePromises[String(id)] = responsePromise;
|
|
}
|
|
});
|
|
if (token) {
|
|
token.onCancellationRequested(() => {
|
|
connection.sendNotification(CancelNotification.type, { id });
|
|
});
|
|
}
|
|
return result;
|
|
},
|
|
onRequest: (type, handler) => {
|
|
throwIfClosedOrDisposed();
|
|
if (Is.func(type)) {
|
|
starRequestHandler = type;
|
|
}
|
|
else if (handler) {
|
|
if (Is.string(type)) {
|
|
requestHandlers[type] = { type: undefined, handler };
|
|
}
|
|
else {
|
|
requestHandlers[type.method] = { type, handler };
|
|
}
|
|
}
|
|
},
|
|
trace: (_value, _tracer, sendNotificationOrTraceOptions) => {
|
|
let _sendNotification = false;
|
|
let _traceFormat = TraceFormat.Text;
|
|
if (sendNotificationOrTraceOptions !== void 0) {
|
|
if (Is.boolean(sendNotificationOrTraceOptions)) {
|
|
_sendNotification = sendNotificationOrTraceOptions;
|
|
}
|
|
else {
|
|
_sendNotification = sendNotificationOrTraceOptions.sendNotification || false;
|
|
_traceFormat = sendNotificationOrTraceOptions.traceFormat || TraceFormat.Text;
|
|
}
|
|
}
|
|
trace = _value;
|
|
traceFormat = _traceFormat;
|
|
if (trace === Trace.Off) {
|
|
tracer = undefined;
|
|
}
|
|
else {
|
|
tracer = _tracer;
|
|
}
|
|
if (_sendNotification && !isClosed() && !isDisposed()) {
|
|
connection.sendNotification(SetTraceNotification.type, { value: Trace.toString(_value) });
|
|
}
|
|
},
|
|
onError: errorEmitter.event,
|
|
onClose: closeEmitter.event,
|
|
onUnhandledNotification: unhandledNotificationEmitter.event,
|
|
onDispose: disposeEmitter.event,
|
|
dispose: () => {
|
|
if (isDisposed()) {
|
|
return;
|
|
}
|
|
state = ConnectionState.Disposed;
|
|
disposeEmitter.fire(undefined);
|
|
let error = new Error('Connection got disposed.');
|
|
Object.keys(responsePromises).forEach((key) => {
|
|
responsePromises[key].reject(error);
|
|
});
|
|
responsePromises = Object.create(null);
|
|
requestTokens = Object.create(null);
|
|
messageQueue = new linkedMap_1.LinkedMap();
|
|
// Test for backwards compatibility
|
|
if (Is.func(messageWriter.dispose)) {
|
|
messageWriter.dispose();
|
|
}
|
|
if (Is.func(messageReader.dispose)) {
|
|
messageReader.dispose();
|
|
}
|
|
},
|
|
listen: () => {
|
|
throwIfClosedOrDisposed();
|
|
throwIfListening();
|
|
state = ConnectionState.Listening;
|
|
messageReader.listen(callback);
|
|
},
|
|
inspect: () => {
|
|
console.log("inspect");
|
|
}
|
|
};
|
|
connection.onNotification(LogTraceNotification.type, (params) => {
|
|
if (trace === Trace.Off || !tracer) {
|
|
return;
|
|
}
|
|
tracer.log(params.message, trace === Trace.Verbose ? params.verbose : undefined);
|
|
});
|
|
return connection;
|
|
}
|
|
function isMessageReader(value) {
|
|
return value.listen !== void 0 && value.read === void 0;
|
|
}
|
|
function isMessageWriter(value) {
|
|
return value.write !== void 0 && value.end === void 0;
|
|
}
|
|
function createMessageConnection(input, output, logger, strategy) {
|
|
if (!logger) {
|
|
logger = exports.NullLogger;
|
|
}
|
|
let reader = isMessageReader(input) ? input : new messageReader_1.StreamMessageReader(input);
|
|
let writer = isMessageWriter(output) ? output : new messageWriter_1.StreamMessageWriter(output);
|
|
return _createMessageConnection(reader, writer, logger, strategy);
|
|
}
|
|
exports.createMessageConnection = createMessageConnection;
|