Files
vim-netbeans-server/lib/vim-nb-protocol.js

312 lines
9.6 KiB
JavaScript

var Transform = require("stream").Transform;
var EventEmitter = require("events").EventEmitter;
/**
* A Transform Stream implementation that output full VNB protocol messages
*
* @constructor
* @this {VNBMessageParser}
*/
function VNBMessageTransform(opts) {
"use strict";
Transform.call(this,opts);
var buffer;
/**
* Transform stream Write implementation
* @param {string} data Upstream data already converted as string
* @param {string} encoding String encoding (should be utf8, but should be checked upward)
* @param {function} callback Downstream Stream receiving only full VNB messages
**/
this._transform = function(data, encoding, callback) {
var endpos = data.indexOf("\n");
if (endpos > -1) {
if (buffer !== undefined) {
this.push(buffer);
buffer = undefined;
}
if (endpos > 0) {
this.push(data.substr(0, endpos));
}
callback();
if (endpos < data.length - 1) {
buffer = data.substr(endpos + 1);
}
}
else {
buffer = buffer.concat(data);
}
};
/**
* Flush still data. Remaining data is lost since it can only be an incomplete message
* @param {function} callback Callback to call when data has been flushed
**/
this._flush = function (callback) {
buffer = undefined;
callback();
};
}
/**
* Authentication callback
* @callback authenticationCallback
* @param {string} password Password given by the authenticating client
* @return {bool} Result of the authentication
*/
/**
* VNB Client implementation (communication from vim to the netbeans server)
* @constructor
* @this {VNBMessageClient}
* @param {Socket} socket Incomming stream
* @param {authenticationCallback} authentication Callback function called when the AUTH message is received
*/
function VNBClient(socket, authentication) {
"use strict";
EventEmitter.call(this);
var self = this;
var callbacks = new Map();
var currentSeqno = 0;
var connected = false;
//TODO check if this is needed
socket.setEncoding("utf8");
var messageTransform = new VNBMessageTransform();
messageTransform.on("data",function (message) {
//The first message must be an authentication one
if (message.startsWith("AUTH ") && authentication(message.substring(message.indexOf(" ") + 1))) {
connected = true;
messageTransform.removeAllListeners("data");
messageTransform.on("data", function(message) {
try {
var iName = 0; //Index of the name separator in case of an Event
var iSeqno = 0; //Index of the seqno separator in case of an Event
//Header of the message
for (var i = 0 ; i < message.length && message[i] !== " "; ++i) {
if (message[i] === ":") {
iName = i;
}
else if (message[i] === "=") {
iSeqno = i;
}
}
var isEvent = false;
var args = [];
var seqno = 0;
if (iName > 0 && iSeqno > 0) {
isEvent = true;
args.push("event");
args.push(Number.parseInt(message.substr(0, iName), 10));
args.push(message.substring(iName + 1, iSeqno));
//args.push(Number.parseInt(message.substring(iSeqno + 1, i), 10));
}
else if (iName === 0 && iSeqno === 0) {
seqno = Number.parseInt(message.substr(0, i), 10);
}
else {
throw "Unknown message type";
}
var argStartOffset = ++i;
//Arguments of the message
for ( ; i < message.length ; ++i) {
if (message[i] === "\"" || message[i] === "!") {
var parsedString = "";
i += 1;
while (i < message.length && message[i] !== "\"") {
if (message[i] === "\\") {
i += 1;
if (i < message.length) {
throw "Unfinished string in message";
}
else {
switch (message[i]) {
case "n" :
parsedString += "\n";
break;
case "\\" :
parsedString += "\\";
break;
case "\"" :
parsedString += "\"";
break;
case "r" :
parsedString += "\r";
break;
case "t" :
parsedString += "\t";
break;
default :
throw "Invalid escaped character in string";
}
}
}
else {
parsedString += message[i];
}
++i;
}
args.push(parsedString);
i += 1;
}
else {
while (i < message.length && message[i] !== " ") {
i += 1;
}
if (i - argStartOffset === 1 && (message[i] === "T" || message[i] === "F")) {
args.push(message[i] === "T");
}
else {
var argument = message.substr(argStartOffset, i);
if (Number.NaN(argument)) {
if (argument.contains("/")) {
args.push(argument.split("/"));
}
else {
args.push(argument);
}
}
else {
args.push(Number.parseInt(argument));
}
}
}
}
if (isEvent) {
//Emit the event
self.emit(...args);
}
else if (callbacks.has(seqno)) {
callbacks.get(seqno)(...args);
callbacks.delete(seqno);
}
else {
throw "Received reply to unknown function call";
}
}
catch (ex) {
self.emit("error", ex.message);
}
});
}
else {
//TODO send a message to client ?
socket.end();
}
});
messageTransform.on("error", function (error) {
});
messageTransform.on("end",function () {
});
messageTransform.on("close", function () {
});
self.sendCommand = function (name, buffer) {
};
self.callFunction = function (name, buffer) {
let seqno = currentSeqno;
currentSeqno += 1;
socket.write()
};
socket.pipe(messageTransform);
}
function NetbeansColor (value) {
"use strict";
this.convert = function() {
return value ? value : "none";
};
}
function NetbeansBool (value) {
"use strict";
this.convert = function () {
return value ? "T" : "F";
};
}
function NetbeansPosition (line, col) {
"use strict";
this.convert = function () {
return line+"/"+col;
};
}
function NetbeansString (value) {
"use strict";
this.convert = function () {
var buffer = "\"";
for (var i = 0 ; i < value.length ; ++i){
switch (value[i]) {
case "\n" :
buffer += "\\n";
break;
case "\t" :
buffer += "\\t";
break;
case "\r" :
buffer += "\\r";
break;
case "\\" :
buffer += "\\\\";
break;
case "\"" :
buffer += "\\\"";
break;
default :
buffer += value[i];
break;
}
}
buffer += "\"";
return buffer;
}
}
VNBClient.prototype = EventEmitter;