Full implementation of the procol
This commit is contained in:
383
lib/controler.js
Normal file
383
lib/controler.js
Normal file
@@ -0,0 +1,383 @@
|
||||
var Transform = require("stream").Transform;
|
||||
var EventEmitter = require("events").EventEmitter;
|
||||
var Types = require("types");
|
||||
var Buffer = require("buffer");
|
||||
|
||||
|
||||
/**
|
||||
* A Transform Stream implementation that output full VNB protocol messages
|
||||
* @constructor
|
||||
* @param {object} opts Options passed to the Transform constructor
|
||||
*/
|
||||
function MessageTransform(opts) {
|
||||
"use strict";
|
||||
Transform.call(this,opts);
|
||||
|
||||
var buffer;
|
||||
|
||||
/**
|
||||
* Transform stream Write implementation
|
||||
* @function
|
||||
* @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
|
||||
* @function
|
||||
* @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
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Client implementation for vim netbeans protocol (communication from vim to the netbeans server)
|
||||
* @constructor
|
||||
* @param {Socket} socket Incomming stream
|
||||
* @param {authenticationCallback} authentication Callback function called when the AUTH message is received
|
||||
*/
|
||||
function Controler(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 MessageTransform();
|
||||
|
||||
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 () {
|
||||
|
||||
});
|
||||
|
||||
/**
|
||||
* Send a command message
|
||||
* @function sendCommand
|
||||
* @param {string} name Name of the command
|
||||
* @param {number} buffer Name of the buffer
|
||||
* @param {...string} arguments Parameters of the command
|
||||
*/
|
||||
this.sendCommand = function(name, buffer) {
|
||||
let seqno = currentSeqno;
|
||||
currentSeqno += 1;
|
||||
|
||||
var data = buffer + ":" + name + ":" + seqno;
|
||||
|
||||
for (var i = 2 ; i < arguments.length ; ++i) {
|
||||
data += " " + arguments[i];
|
||||
}
|
||||
|
||||
data += "\n";
|
||||
|
||||
socket.write(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a function message
|
||||
* @function callFunction
|
||||
* @param {string} name Name of the function
|
||||
* @param {number} buffer ID of the buffer
|
||||
* @param {...string} arguments Parameters of the function. Use the string, bool, color and position function
|
||||
* of the method to convert javascript types to string
|
||||
* @param {function} callback Callback function called with reply value
|
||||
* */
|
||||
this.callFunction = function(name, buffer) {
|
||||
let seqno = currentSeqno;
|
||||
currentSeqno += 1;
|
||||
|
||||
var lastArg = arguments.length - 1;
|
||||
|
||||
//If the function is called with a callback (wich should always be the case)
|
||||
//add the calback to the map so it's fired when the reply is received
|
||||
if (typeof(arguments[lastArg]) === "function") {
|
||||
callbacks[currentSeqno] = arguments[lastArg];
|
||||
lastArg -= 1;
|
||||
}
|
||||
|
||||
var data = buffer + ":" + name + "/" + seqno;
|
||||
|
||||
for (var i = 2 ; i < lastArg ; ++i) {
|
||||
data += " " + arguments[i];
|
||||
}
|
||||
|
||||
data += "\n";
|
||||
|
||||
socket.write(data);
|
||||
|
||||
};
|
||||
|
||||
socket.pipe(messageTransform);
|
||||
}
|
||||
|
||||
InternalClient.prototype = EventEmitter;
|
||||
|
||||
/**
|
||||
* Connexion with a vim client
|
||||
* @constructor
|
||||
* @param {Socket} socket Connexion's socket
|
||||
* @param {authenticationCallback} authentication Callback used to authenticate the client
|
||||
*/
|
||||
function Client(socket, authentication) {
|
||||
"use strict";
|
||||
|
||||
var client = new Controler(socket, authentication);
|
||||
var buffId = 0;
|
||||
|
||||
/* GLOBAL COMMANDS */
|
||||
|
||||
this.startAtomic = function () {
|
||||
client.sendCommand("startAtomic", 0);
|
||||
};
|
||||
|
||||
this.endAtomic = function() {
|
||||
client.sendCommand("endAtmoic", 0);
|
||||
};
|
||||
|
||||
this.raise = function () {
|
||||
client.sendCommand("raise", 0);
|
||||
};
|
||||
|
||||
this.setExitDelay = function (seconds) {
|
||||
client.sendCommand("setExitDelay", 0, seconds);
|
||||
};
|
||||
|
||||
this.specialKeys = function (keys) {
|
||||
client.sendCommand("specialKeys", 0,Types.string(keys)); //TODO : Wild guess, no documentation about this one
|
||||
};
|
||||
|
||||
this.create = function() {
|
||||
var buffer = new Buffer(client, buffId);
|
||||
buffId += 1;
|
||||
client.sendCommand("create", buffId);
|
||||
return buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* putBufferNumber
|
||||
* @function
|
||||
* @param {string} pathname Path of the file whose buffer is identified
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.putBufferNumber = function (pathname) {
|
||||
var buffer = new Buffer(client, buffId);
|
||||
buffId += 1;
|
||||
client.sendCommand("putBufferNumber", buffId, Types.string(pathname)); //TODO : May be better on the buffer
|
||||
return buffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set a buffer number to a file opened by Vim and sets its buffer as current buffer
|
||||
* @function
|
||||
* @param {string} pathname Path of the file
|
||||
*/
|
||||
this.setBufferNumber = function (pathname) {
|
||||
var buffer = new Buffer(client, buffId);
|
||||
buffId += 1;
|
||||
client.sendCommand("setBufferNumber", buffId, Types.string(pathname));
|
||||
return buffer;
|
||||
};
|
||||
|
||||
/* GLOBAL FUNCTIONS */
|
||||
|
||||
/**
|
||||
* saveAndExit
|
||||
*
|
||||
* @param callback
|
||||
* @return {undefined}
|
||||
*/
|
||||
this.saveAndExit = function (callback) {
|
||||
client.callFunction("saveAndExit", 0, callback);
|
||||
};
|
||||
|
||||
this.getModified = function (callback) {
|
||||
client.callFunction("getModified", 0, callback);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.Controler = Client;
|
||||
Reference in New Issue
Block a user