|
@@ -7,261 +7,260 @@ import js.node.stream.Readable;
|
|
|
using StringTools;
|
|
|
|
|
|
class ErrorUtils {
|
|
|
- public static function errorToString(error:Dynamic, intro:String):String {
|
|
|
- var result = intro + Std.string(error);
|
|
|
- var stack = haxe.CallStack.exceptionStack();
|
|
|
- if (stack != null && stack.length > 0)
|
|
|
- result += "\n" + haxe.CallStack.toString(stack);
|
|
|
- return result;
|
|
|
- }
|
|
|
+ public static function errorToString(error:Dynamic, intro:String):String {
|
|
|
+ var result = intro + Std.string(error);
|
|
|
+ var stack = haxe.CallStack.exceptionStack();
|
|
|
+ if (stack != null && stack.length > 0)
|
|
|
+ result += "\n" + haxe.CallStack.toString(stack);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
private class DisplayRequest {
|
|
|
- // these are used for the queue
|
|
|
- public var prev:DisplayRequest;
|
|
|
- public var next:DisplayRequest;
|
|
|
-
|
|
|
- var args:Array<String>;
|
|
|
- var stdin:String;
|
|
|
- var callback:String->Void;
|
|
|
- var errback:String->Void;
|
|
|
-
|
|
|
- static var stdinSepBuf = new Buffer([1]);
|
|
|
-
|
|
|
- public function new(args:Array<String>, stdin:String, callback:String->Void, errback:String->Void) {
|
|
|
- this.args = args;
|
|
|
- this.stdin = stdin;
|
|
|
- this.callback = callback;
|
|
|
- this.errback = errback;
|
|
|
- }
|
|
|
-
|
|
|
- public function prepareBody():Buffer {
|
|
|
- if (stdin != null) {
|
|
|
- args.push("-D");
|
|
|
- args.push("display-stdin");
|
|
|
- }
|
|
|
-
|
|
|
- var lenBuf = new Buffer(4);
|
|
|
- var chunks = [lenBuf];
|
|
|
- var length = 0;
|
|
|
- for (arg in args) {
|
|
|
- var buf = new Buffer(arg + "\n");
|
|
|
- chunks.push(buf);
|
|
|
- length += buf.length;
|
|
|
- }
|
|
|
-
|
|
|
- if (stdin != null) {
|
|
|
- chunks.push(stdinSepBuf);
|
|
|
- var buf = new Buffer(stdin);
|
|
|
- chunks.push(buf);
|
|
|
- length += buf.length + stdinSepBuf.length;
|
|
|
- }
|
|
|
-
|
|
|
- lenBuf.writeInt32LE(length, 0);
|
|
|
-
|
|
|
- return Buffer.concat(chunks, length + 4);
|
|
|
- }
|
|
|
-
|
|
|
- public function processResult(context:Context, data:String) {
|
|
|
- var buf = new StringBuf();
|
|
|
- var hasError = false;
|
|
|
- for (line in data.split("\n")) {
|
|
|
- switch (line.fastCodeAt(0)) {
|
|
|
- case 0x01: // print
|
|
|
- var line = line.substring(1).replace("\x01", "\n");
|
|
|
+ // these are used for the queue
|
|
|
+ public var prev:DisplayRequest;
|
|
|
+ public var next:DisplayRequest;
|
|
|
+
|
|
|
+ var args:Array<String>;
|
|
|
+ var stdin:String;
|
|
|
+ var callback:String->Void;
|
|
|
+ var errback:String->Void;
|
|
|
+
|
|
|
+ static var stdinSepBuf = new Buffer([1]);
|
|
|
+
|
|
|
+ public function new(args:Array<String>, stdin:String, callback:String->Void, errback:String->Void) {
|
|
|
+ this.args = args;
|
|
|
+ this.stdin = stdin;
|
|
|
+ this.callback = callback;
|
|
|
+ this.errback = errback;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function prepareBody():Buffer {
|
|
|
+ if (stdin != null) {
|
|
|
+ args.push("-D");
|
|
|
+ args.push("display-stdin");
|
|
|
+ }
|
|
|
+
|
|
|
+ var lenBuf = new Buffer(4);
|
|
|
+ var chunks = [lenBuf];
|
|
|
+ var length = 0;
|
|
|
+ for (arg in args) {
|
|
|
+ var buf = new Buffer(arg + "\n");
|
|
|
+ chunks.push(buf);
|
|
|
+ length += buf.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (stdin != null) {
|
|
|
+ chunks.push(stdinSepBuf);
|
|
|
+ var buf = new Buffer(stdin);
|
|
|
+ chunks.push(buf);
|
|
|
+ length += buf.length + stdinSepBuf.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ lenBuf.writeInt32LE(length, 0);
|
|
|
+
|
|
|
+ return Buffer.concat(chunks, length + 4);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function processResult(context:Context, data:String) {
|
|
|
+ var buf = new StringBuf();
|
|
|
+ var hasError = false;
|
|
|
+ for (line in data.split("\n")) {
|
|
|
+ switch (line.fastCodeAt(0)) {
|
|
|
+ case 0x01: // print
|
|
|
+ var line = line.substring(1).replace("\x01", "\n");
|
|
|
context.sendLogMessage("Haxe print: " + line + "\n");
|
|
|
- case 0x02: // error
|
|
|
- hasError = true;
|
|
|
- default:
|
|
|
- buf.add(line);
|
|
|
- buf.addChar("\n".code);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- var data = buf.toString().trim();
|
|
|
-
|
|
|
- if (hasError)
|
|
|
- return errback(data);
|
|
|
-
|
|
|
- try {
|
|
|
- callback(data);
|
|
|
- } catch (e:Any) {
|
|
|
- errback(ErrorUtils.errorToString(e, "Exception while handling Haxe completion response: "));
|
|
|
- }
|
|
|
- }
|
|
|
+ case 0x02: // error
|
|
|
+ hasError = true;
|
|
|
+ default:
|
|
|
+ buf.add(line);
|
|
|
+ buf.addChar("\n".code);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ var data = buf.toString().trim();
|
|
|
+
|
|
|
+ if (hasError)
|
|
|
+ return errback(data);
|
|
|
+
|
|
|
+ try {
|
|
|
+ callback(data);
|
|
|
+ } catch (e:Any) {
|
|
|
+ errback(ErrorUtils.errorToString(e, "Exception while handling Haxe completion response: "));
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
typedef DisplayServerConfigBase = {
|
|
|
- var haxePath:String;
|
|
|
- var arguments:Array<String>;
|
|
|
- var env:haxe.DynamicAccess<String>;
|
|
|
+ var haxePath:String;
|
|
|
+ var arguments:Array<String>;
|
|
|
+ var env:haxe.DynamicAccess<String>;
|
|
|
}
|
|
|
|
|
|
typedef Context = {
|
|
|
- function sendErrorMessage(msg:String):Void;
|
|
|
- function sendLogMessage(msg:String):Void;
|
|
|
- var displayServerConfig:DisplayServerConfigBase;
|
|
|
+ function sendErrorMessage(msg:String):Void;
|
|
|
+ function sendLogMessage(msg:String):Void;
|
|
|
+ var displayServerConfig:DisplayServerConfigBase;
|
|
|
}
|
|
|
|
|
|
private class MessageBuffer {
|
|
|
- static inline var DEFAULT_SIZE = 8192;
|
|
|
-
|
|
|
- var index:Int;
|
|
|
- var buffer:Buffer;
|
|
|
-
|
|
|
- public function new() {
|
|
|
- index = 0;
|
|
|
- buffer = new Buffer(DEFAULT_SIZE);
|
|
|
- }
|
|
|
-
|
|
|
- public function append(chunk:Buffer):Void {
|
|
|
- if (buffer.length - index >= chunk.length) {
|
|
|
- chunk.copy(buffer, index, 0, chunk.length);
|
|
|
- } else {
|
|
|
- var newSize = (Math.ceil((index + chunk.length) / DEFAULT_SIZE) + 1) * DEFAULT_SIZE;
|
|
|
- if (index == 0) {
|
|
|
- buffer = new Buffer(newSize);
|
|
|
- chunk.copy(buffer, 0, 0, chunk.length);
|
|
|
- } else {
|
|
|
- buffer = Buffer.concat([buffer.slice(0, index), chunk], newSize);
|
|
|
- }
|
|
|
- }
|
|
|
- index += chunk.length;
|
|
|
- }
|
|
|
-
|
|
|
- public function tryReadLength():Int {
|
|
|
- if (index < 4)
|
|
|
- return -1;
|
|
|
- var length = buffer.readInt32LE(0);
|
|
|
- buffer = buffer.slice(4);
|
|
|
- index -= 4;
|
|
|
- return length;
|
|
|
- }
|
|
|
-
|
|
|
- public function tryReadContent(length:Int):String {
|
|
|
- if (index < length)
|
|
|
- return null;
|
|
|
- var result = buffer.toString("utf-8", 0, length);
|
|
|
- var nextStart = length;
|
|
|
- buffer.copy(buffer, 0, nextStart);
|
|
|
- index -= nextStart;
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- public function getContent():String {
|
|
|
- return buffer.toString("utf-8", 0, index);
|
|
|
- }
|
|
|
+ static inline var DEFAULT_SIZE = 8192;
|
|
|
+
|
|
|
+ var index:Int;
|
|
|
+ var buffer:Buffer;
|
|
|
+
|
|
|
+ public function new() {
|
|
|
+ index = 0;
|
|
|
+ buffer = new Buffer(DEFAULT_SIZE);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function append(chunk:Buffer):Void {
|
|
|
+ if (buffer.length - index >= chunk.length) {
|
|
|
+ chunk.copy(buffer, index, 0, chunk.length);
|
|
|
+ } else {
|
|
|
+ var newSize = (Math.ceil((index + chunk.length) / DEFAULT_SIZE) + 1) * DEFAULT_SIZE;
|
|
|
+ if (index == 0) {
|
|
|
+ buffer = new Buffer(newSize);
|
|
|
+ chunk.copy(buffer, 0, 0, chunk.length);
|
|
|
+ } else {
|
|
|
+ buffer = Buffer.concat([buffer.slice(0, index), chunk], newSize);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ index += chunk.length;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function tryReadLength():Int {
|
|
|
+ if (index < 4)
|
|
|
+ return -1;
|
|
|
+ var length = buffer.readInt32LE(0);
|
|
|
+ buffer = buffer.slice(4);
|
|
|
+ index -= 4;
|
|
|
+ return length;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function tryReadContent(length:Int):String {
|
|
|
+ if (index < length)
|
|
|
+ return null;
|
|
|
+ var result = buffer.toString("utf-8", 0, length);
|
|
|
+ var nextStart = length;
|
|
|
+ buffer.copy(buffer, 0, nextStart);
|
|
|
+ index -= nextStart;
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function getContent():String {
|
|
|
+ return buffer.toString("utf-8", 0, index);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
class HaxeServer {
|
|
|
- var proc:ChildProcessObject;
|
|
|
-
|
|
|
- var context:Context;
|
|
|
- var buffer:MessageBuffer;
|
|
|
- var nextMessageLength:Int;
|
|
|
-
|
|
|
- var requestsHead:DisplayRequest;
|
|
|
- var requestsTail:DisplayRequest;
|
|
|
- var currentRequest:DisplayRequest;
|
|
|
-
|
|
|
- public function new(context:Context) {
|
|
|
- this.context = context;
|
|
|
- }
|
|
|
-
|
|
|
- static var reTrailingNewline = ~/\r?\n$/;
|
|
|
-
|
|
|
- public function start() {
|
|
|
- stop();
|
|
|
-
|
|
|
- inline function error(s) context.sendErrorMessage(s);
|
|
|
-
|
|
|
- var env = new haxe.DynamicAccess();
|
|
|
- for (key in js.Node.process.env.keys())
|
|
|
- env[key] = js.Node.process.env[key];
|
|
|
-
|
|
|
- buffer = new MessageBuffer();
|
|
|
- nextMessageLength = -1;
|
|
|
-
|
|
|
- proc = ChildProcess.spawn(context.displayServerConfig.haxePath, context.displayServerConfig.arguments.concat(["--wait", "stdio"]), {env: env});
|
|
|
-
|
|
|
- proc.stdout.on(ReadableEvent.Data, function(buf:Buffer) {
|
|
|
- context.sendLogMessage(reTrailingNewline.replace(buf.toString(), ""));
|
|
|
- });
|
|
|
- proc.stderr.on(ReadableEvent.Data, onData);
|
|
|
-
|
|
|
- proc.on(ChildProcessEvent.Exit, onExit);
|
|
|
- }
|
|
|
-
|
|
|
- public function stop() {
|
|
|
- if (proc != null) {
|
|
|
- proc.removeAllListeners();
|
|
|
- proc.kill();
|
|
|
- proc = null;
|
|
|
- }
|
|
|
-
|
|
|
- requestsHead = requestsTail = currentRequest = null;
|
|
|
- }
|
|
|
-
|
|
|
- public function restart(reason:String) {
|
|
|
- context.sendLogMessage('Restarting Haxe completion server: $reason');
|
|
|
- start();
|
|
|
- }
|
|
|
-
|
|
|
- function onExit(_, _) {
|
|
|
- var haxeResponse = buffer.getContent();
|
|
|
- trace("\nError message from the compiler:\n");
|
|
|
- trace(haxeResponse);
|
|
|
- }
|
|
|
-
|
|
|
- function onData(data:Buffer) {
|
|
|
- buffer.append(data);
|
|
|
- while (true) {
|
|
|
- if (nextMessageLength == -1) {
|
|
|
- var length = buffer.tryReadLength();
|
|
|
- if (length == -1)
|
|
|
- return;
|
|
|
- nextMessageLength = length;
|
|
|
- }
|
|
|
- var msg = buffer.tryReadContent(nextMessageLength);
|
|
|
- if (msg == null)
|
|
|
- return;
|
|
|
- nextMessageLength = -1;
|
|
|
- if (currentRequest != null) {
|
|
|
- var request = currentRequest;
|
|
|
- currentRequest = null;
|
|
|
- request.processResult(context, msg);
|
|
|
- checkQueue();
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public function process(args:Array<String>, stdin:String, callback:String->Void, errback:String->Void) {
|
|
|
- // create a request object
|
|
|
- var request = new DisplayRequest(args, stdin, callback, errback);
|
|
|
-
|
|
|
- // add to the queue
|
|
|
- if (requestsHead == null) {
|
|
|
- requestsHead = requestsTail = request;
|
|
|
- } else {
|
|
|
- requestsTail.next = request;
|
|
|
- request.prev = requestsTail;
|
|
|
- requestsTail = request;
|
|
|
- }
|
|
|
-
|
|
|
- // process the queue
|
|
|
- checkQueue();
|
|
|
- }
|
|
|
-
|
|
|
- function checkQueue() {
|
|
|
- // there's a currently processing request, wait and don't send another one to Haxe
|
|
|
- if (currentRequest != null)
|
|
|
- return;
|
|
|
-
|
|
|
- // pop the first request still in queue, set it as current and send to Haxe
|
|
|
- if (requestsHead != null) {
|
|
|
- currentRequest = requestsHead;
|
|
|
- requestsHead = currentRequest.next;
|
|
|
- proc.stdin.write(currentRequest.prepareBody());
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
+ var proc:ChildProcessObject;
|
|
|
+ var context:Context;
|
|
|
+ var buffer:MessageBuffer;
|
|
|
+ var nextMessageLength:Int;
|
|
|
+ var requestsHead:DisplayRequest;
|
|
|
+ var requestsTail:DisplayRequest;
|
|
|
+ var currentRequest:DisplayRequest;
|
|
|
+
|
|
|
+ public function new(context:Context) {
|
|
|
+ this.context = context;
|
|
|
+ }
|
|
|
+
|
|
|
+ static var reTrailingNewline = ~/\r?\n$/;
|
|
|
+
|
|
|
+ public function start() {
|
|
|
+ stop();
|
|
|
+
|
|
|
+ inline function error(s)
|
|
|
+ context.sendErrorMessage(s);
|
|
|
+
|
|
|
+ var env = new haxe.DynamicAccess();
|
|
|
+ for (key in js.Node.process.env.keys())
|
|
|
+ env[key] = js.Node.process.env[key];
|
|
|
+
|
|
|
+ buffer = new MessageBuffer();
|
|
|
+ nextMessageLength = -1;
|
|
|
+
|
|
|
+ proc = ChildProcess.spawn(context.displayServerConfig.haxePath, context.displayServerConfig.arguments.concat(["--wait", "stdio"]), {env: env});
|
|
|
+
|
|
|
+ proc.stdout.on(ReadableEvent.Data, function(buf:Buffer) {
|
|
|
+ context.sendLogMessage(reTrailingNewline.replace(buf.toString(), ""));
|
|
|
+ });
|
|
|
+ proc.stderr.on(ReadableEvent.Data, onData);
|
|
|
+
|
|
|
+ proc.on(ChildProcessEvent.Exit, onExit);
|
|
|
+ }
|
|
|
+
|
|
|
+ public function stop() {
|
|
|
+ if (proc != null) {
|
|
|
+ proc.removeAllListeners();
|
|
|
+ proc.kill();
|
|
|
+ proc = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ requestsHead = requestsTail = currentRequest = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public function restart(reason:String) {
|
|
|
+ context.sendLogMessage('Restarting Haxe completion server: $reason');
|
|
|
+ start();
|
|
|
+ }
|
|
|
+
|
|
|
+ function onExit(_, _) {
|
|
|
+ var haxeResponse = buffer.getContent();
|
|
|
+ trace("\nError message from the compiler:\n");
|
|
|
+ trace(haxeResponse);
|
|
|
+ }
|
|
|
+
|
|
|
+ function onData(data:Buffer) {
|
|
|
+ buffer.append(data);
|
|
|
+ while (true) {
|
|
|
+ if (nextMessageLength == -1) {
|
|
|
+ var length = buffer.tryReadLength();
|
|
|
+ if (length == -1)
|
|
|
+ return;
|
|
|
+ nextMessageLength = length;
|
|
|
+ }
|
|
|
+ var msg = buffer.tryReadContent(nextMessageLength);
|
|
|
+ if (msg == null)
|
|
|
+ return;
|
|
|
+ nextMessageLength = -1;
|
|
|
+ if (currentRequest != null) {
|
|
|
+ var request = currentRequest;
|
|
|
+ currentRequest = null;
|
|
|
+ request.processResult(context, msg);
|
|
|
+ checkQueue();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public function process(args:Array<String>, stdin:String, callback:String->Void, errback:String->Void) {
|
|
|
+ // create a request object
|
|
|
+ var request = new DisplayRequest(args, stdin, callback, errback);
|
|
|
+
|
|
|
+ // add to the queue
|
|
|
+ if (requestsHead == null) {
|
|
|
+ requestsHead = requestsTail = request;
|
|
|
+ } else {
|
|
|
+ requestsTail.next = request;
|
|
|
+ request.prev = requestsTail;
|
|
|
+ requestsTail = request;
|
|
|
+ }
|
|
|
+
|
|
|
+ // process the queue
|
|
|
+ checkQueue();
|
|
|
+ }
|
|
|
+
|
|
|
+ function checkQueue() {
|
|
|
+ // there's a currently processing request, wait and don't send another one to Haxe
|
|
|
+ if (currentRequest != null)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // pop the first request still in queue, set it as current and send to Haxe
|
|
|
+ if (requestsHead != null) {
|
|
|
+ currentRequest = requestsHead;
|
|
|
+ requestsHead = currentRequest.next;
|
|
|
+ proc.stdin.write(currentRequest.prepareBody());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|