Node.js REPL not working as Brackets extension through NodeDomain

I am trying to build an extension for the Brackets editor which is a Node.js REPL, similar to the SublimeREPL extension for Sublime Text.

I understand from the docs that I need to implement the REPL as a NodeDomain to make it accessible to Brackets and I used the example extension as a base for mine.

Unfortunately, when I send some Javascript code to the REPL it is not evaluated and only the prompt text I set is returned.

My idea for the setup of the extension is as follows:

  • from Brackets I send some javascript code as a string to the Node side, where it is given as an argument to the command handler of the NodeDomain
    • this code is pushed into a readable stream which is set as the input of the REPL
    • the output of the REPL is connected to a writable stream which pushes its output (the evaluated javascript code) into another readable stream
    • the command handler of the NodeDomain listens for the 'data' event of this second readable stream and invokes its callback (the second argument) with the data (the evaluated code) to return it back to Brackets

However, the result only contains the prompt text and not the evaluated code.

Here's the code for the NodeDomain:

ReplDomain.js

(function () {
    "use strict";

    var net = require('net');
    var Readable = require('stream').Readable;
    var Writable = require('stream').Writable;
    var repl = require("repl");

    // inputstream for REPL, input code will be pushed into this stream from Brackets
    var ReplInput = new Readable();
    ReplInput._read = function noop() {};


    // outputstream for REPL, any output will be pushed into the CallbackInput stream so when the 'data' event is emitted
    // the 'done()' callback in cmdExecRepl will be called with the REPL output and retured to Brackets
    var ReplOutput = new Writable();
    ReplOutput._write = function noop(data, enc, next) {
        CallbackInput.push(data);
        CallbackInput.push(null);
        next();
    };

    var CallbackInput = new Readable();
    CallbackInput._read = function noop() {};


    //setup REPL
    var r = repl.start({
        prompt: 'node repl> ',
        input: ReplInput,
        output: ReplOutput,
        terminal: false,
        useGlobal: false
    });



    /**
     * Handler function for the repl.execRepl command.
     * 
    */
    function cmdExecRepl(code, done) {

        // code from Brackets is pushed into the ReplInput readable stream and so put into the REPL
        ReplInput.push(code);
        ReplInput.push(null);

        // the output from the REPL is pushed into the CallbackInput readable stream so we can detect the
        // 'data' event and return the evaluated code to Brackets
        CallbackInput.on('data', function(data) {
            console.log('evaluated code:', data.toString('binary'));
            // data contains just the prompt, but NOT the evaluated code
            done(null, data.toString('binary')); 
        });
    }

    /**
     * Initializes the domain 
     */
    function init(domainManager) {
        if (!domainManager.hasDomain("customRepl")) {
            domainManager.registerDomain("customRepl");
        }
        domainManager.registerCommand(
            "customRepl",       // domain name
            "doRepl",    // command name
            cmdExecRepl,   // command handler function
            true          // this command is synchronous in Node
        );
    }

    exports.init = init;

}());

I wrote a test script that simply logs the REPL output to the console to verify this setup and it seems to work. This is my test script:

repl-test.js

"use strict";

var Readable = require('stream').Readable;
var Writable = require('stream').Writable;
var repl = require("repl");
var fs = require('fs');

// inputstream for REPL, input code will be pushed into this stream 
var ReplInput = new Readable();
ReplInput._read = function noop() {};

// outputstream for REPL, any output will be pushed into the CallbackInput stream so we can detect the 'data'
// event and get the data
var ReplOutput = new Writable();
ReplOutput._write = function (data, enc, next) {
    CallbackInput.push(data);
    // when I add the line below I only get the prompt text back
    // CallbackInput.push(null);
    next();
};


var CallbackInput = new Readable();
CallbackInput._read = function noop() {};

// THIS WORKS: I get the actual REPL output here
CallbackInput.on('data', function(data) {
    console.log('output from REPL:', data.toString());
});

// start the REPL
var r = repl.start({
    prompt: 'node repl> ',
    input: ReplInput,
    output: ReplOutput,
    terminal: false
});


// feed the REPL some code to evaluate
var code = '[1,2,3].join(" ")';
ReplInput.push(code);
ReplInput.push(null);

Output of this script:

output from REPL: node repl> 
output from REPL: '1 2 3'

output from REPL: node repl> 

Note that the script outputs the prompt text as the last line. I suspect that this is the only line that is returned to Brackets so that would explain why I only get the prompt back, I just don't know why this is the case.

At first I had the line:

CallbackInput.push(null);

in ReplOutput._write and that also caused the output of the REPL to only return the prompt text, but when I remove that same line from ReplDomain.js it does not help.

Another thing I tried is to set the "terminal" option of the REPL to true which causes the 'data' event on CallbackInput to be emitted for every single character, but that also doesn't change what is sent back to Brackets.

Perhaps I'm not using streams the right way but so far I cannot find what is going wrong here.

Any help is greatly appreciated!