diff --git a/node_modules/vfs-local/localfs.js b/node_modules/vfs-local/localfs.js index 1984e00d..ab4613bc 100644 --- a/node_modules/vfs-local/localfs.js +++ b/node_modules/vfs-local/localfs.js @@ -1448,17 +1448,8 @@ module.exports = function setup(fsOptions) { function spawn(executablePath, options, callback) { var args = options.args || []; - - if (options.hasOwnProperty('env')) { - options.env.__proto__ = fsOptions.defaultEnv; - } else { - options.env = fsOptions.defaultEnv; - } - if (options.cwd && options.cwd.charAt(0) == "~") - options.cwd = options.env.HOME + options.cwd.substr(1); - if (transformPath && options.cwd) - options.cwd = transformPath(options.cwd); + _setDefaultEnv(options); resolvePath(executablePath, { nocheck : 1, @@ -1495,25 +1486,8 @@ module.exports = function setup(fsOptions) { var args = options.args || []; delete options.args; - if (options.hasOwnProperty('env')) { - options.env.__proto__ = fsOptions.defaultEnv; - } else { - options.env = fsOptions.defaultEnv; - } - - // Pty is only reading from the object itself; - var env = {}; - for (var prop in options.env) { - if (prop == "TMUX") continue; - env[prop] = options.env[prop]; - } - options.env = env; - - if (options.cwd && options.cwd.charAt(0) == "~") - options.cwd = env.HOME + options.cwd.substr(1); - - if (transformPath && options.cwd) - options.cwd = transformPath(options.cwd); + _setDefaultEnv(options); + delete options.env.TMUX; if (options.testing) { args.forEach(function(arg, i){ @@ -1605,36 +1579,29 @@ module.exports = function setup(fsOptions) { * @param {Boolean} [options.base] The base path to store the watch files */ function tmuxspawn(ignored, options, callback) { - var watchFile = join(options.base || "", ".run_" + options.session + ".watch"); - if (watchFile.charAt(0) == "~") - watchFile = join(process.env.HOME, watchFile.substr(1)); - var tmuxName = options.tmuxName || TMUXNAME; + var session = options.session; function fetchPid(callback, retries){ if (!retries) retries = 0; - // use axww to get full command - _execFile("ps", ["ax", "-ww", "-opid,command"], { + _execFile(TMUX, [ + "-u2", "-L", tmuxName, "-C", + "list-panes", "-F", "c9-pid-#{pane_pid}-#{pane_dead}-#{pane_status}", + "-t", session + ], { maxBuffer: 1000 * 1024 }, function(err, stdout){ - var matches = (stdout || "").split("\n").filter(function(line) { - return line.indexOf(watchFile) > -1 - && !/\-L cloud9[\d\.]+ new/.test(line) - && line.indexOf("2> /dev/null") === -1; - }); + var matches = /c9-pid-(\d+)-(\d)-/.exec(stdout); + var isDead = parseInt(matches && matches[2], 10); + var pid = isDead ? 0 : parseInt(matches && matches[1], 10); - // logToFile(options.session + ":" + JSON.stringify(matches)); - - if (!matches.length && retries < 10) { - // logToFile(options.session + ": RETRY MATCH"); + if (!pid && !isDead && retries < 10) { setTimeout(fetchPid.bind(null, callback, ++retries), 30); return; } callback(err, { - pid: matches && matches.length - ? parseInt(matches[0].trim(), 10) - : -1 + pid: pid || -1 }); }); } @@ -1770,7 +1737,7 @@ module.exports = function setup(fsOptions) { // logToFile("Kill: " + options.session); _execFile(TMUX, - ["-L", tmuxName, "kill-session", "-t", options.session], + ["-L", tmuxName, "-C", "kill-session", "-t", options.session], function(err){ if (!options.command) return callback(err, {}); @@ -1828,9 +1795,6 @@ module.exports = function setup(fsOptions) { args = ["-u2", "-L", tmuxName, "new", "-s", options.session]; - if (options.idle) - options.command = "echo '[Idle]'"; - if (options.terminal) { args.push("export ISOUTPUTPANE=0;" + (options.defaultEditor @@ -1838,41 +1802,46 @@ module.exports = function setup(fsOptions) { : "") + BASH + " -l"); } - - else if (options.command) - args.push((BASH + " -l -c '" - + options.command.replace(/'/g, "'\\''") + "'") - + "; ([ -e '" + watchFile + "' ] && " - + "rm '" + watchFile + "')"); + else if (options.idle) { + args.push(BASH + " -l -c 'printf \"\\e[01;34m[Idle]\\e[0m\\n\"" + + "; sleep 0.1;'"); + } + else if (options.command) { + args.push(BASH + " -l -c '" + + options.command.replace(/'/g, "'\\''") + + '; printf "\\e[01;30m\\n\\nProcess exited with code: $?\\e[0m\\n"' + + "; sleep 0.1;'"); + } args.push( - ";", "set-option", "-g", "status", "off", - ";", "set-option", "destroy-unattached", "off", - ";", "set-option", "mouse-select-pane", "on", - ";", "set-option", "set-titles", "on", - ";", "set-option", "quiet", "off", - ";", "set-option", "-g", "prefix", "C-b", - ";", "set-option", "-g", "terminal-overrides", "'xterm:colors=256'" + ";", "set", "-q", "-g", "status", "off", + ";", "set", "-q", "destroy-unattached", "off", + ";", "set", "-q", "mouse-select-pane", "on", + ";", "set", "-q", "set-titles", "on", + ";", "set", "-q", "quiet", "on", + ";", "set", "-q", "-g", "prefix", "C-b", + ";", "set", "-q", "-g", "terminal-overrides", "'xterm:colors=256'" ); // disable buffering of tmux output // TODO investigate if using capture pane after desync is faster - args.push(";", "set-window-option", "c0-change-trigger", "0"); + args.push(";", "setw", "c0-change-trigger", "0"); if (options.output) { args.push( - ";", "set-option", "remain-on-exit", "on", - ";", "set-window-option", "-g", "aggressive-resize", "on" + ";", "set", "-q", "remain-on-exit", "on", + ";", "setw", "-q", "-g", "aggressive-resize", "on" ); } + + if (options.detach && options.output) { + args.unshift("-C"); + args.push(";", "list-panes", "-F", "c9-pid#{pane_pid}-"); + } if (options.detach) args.push(";", "detach"); - var quotedArgs = args.map(function(arg) { - return "'" + arg.replace(/'/g, "'\\''") + "'"; - }).join(" "); - options.env["LD_LIBRARY_PATH"] = (options.env["LD_LIBRARY_PATH"] ? options.env["LD_LIBRARY_PATH"] + ":" : "") + "~/.c9/local/lib"; @@ -1880,31 +1849,30 @@ module.exports = function setup(fsOptions) { options.env["ISOUTPUTPANE"] = "1"; } - if (options.command && !attach && !options.terminal) { - fs.writeFile(watchFile, "-1", "utf8", function(err){ - if (err) { - fs.mkdir(dirname(watchFile), function(err){ - if (err) return callback(err); - fs.writeFile(watchFile, "-1", "utf8", run); - }); - } - else run(); - }); - } - else - run(); - + run(); + function run(err){ if (err) return callback(err); - // Start PTY with TMUX, in a login shell (when not attached) - var EXEC = attach ? TMUX : BASH; - var ARGS = attach - ? args - : [ "-l", "-c", "'" + TMUX + "' " + quotedArgs + " 2> /dev/null"]; + if (options.detach && options.output) { + _setDefaultEnv(options); + delete options.env.TMUX; + + return _execFile(TMUX, args, { + args: args, + name: options.name, + cwd: options.cwd, + resolve: options.resolve, + env: options.env + }, function(err, stdout) { + var m = /c9-pid(\d+)-/.exec(stdout); + var pid = parseInt(m && m[1], 10); + callback(err, {pid: pid}); + }); + } - ptyspawn(EXEC, { - args: ARGS, + ptyspawn(TMUX, { + args: args, name: options.name, cols: options.cols, rows: options.rows, @@ -1926,26 +1894,10 @@ module.exports = function setup(fsOptions) { err.code = "EPERM"; meta.pty.emit("data", err); } - else if (data) { - // Fetch the PID if appropriate - if (options.detach && options.output) { - fetchPid(function(err, pid){ - if (err) return callback(err); - meta.pid = pid.pid; - callback(null, meta); - }); - - if (process.platform != "win32") - meta.pty.kill(); - } - meta.pty.removeListener("data", wait); } }); - - if (options.detach && options.output) - return; } // Return the pty @@ -2081,7 +2033,6 @@ module.exports = function setup(fsOptions) { // Same as tmuxspawn but than spawns bash or other shell, for windows var sessions = {}; function bashspawn(ignored, options, callback) { - var watchFile = join(options.base || "", ".run_" + options.session + ".watch"); var session; function getSessionId(){ @@ -2092,7 +2043,9 @@ module.exports = function setup(fsOptions) { // Fetch PID of a running process and return it if (options.fetchpid) { session = sessions[options.session]; - callback(null, { pid: session && session.pty ? session.pty.pid : -1 }); + setTimeout(function() { + callback(null, { pid: session && session.pty ? session.pty.pid : -1 }); + }, 100); // workaround for late exit message from winpty return; } @@ -2146,29 +2099,21 @@ module.exports = function setup(fsOptions) { sessions[name] = session; if (!session.wait) session.wait = []; - if (options.idle) - options.command = "echo '\033[2J\033[1;1H[Idle]'"; - else if (options.command) - options.command = "echo '\033[2J\033[1;1H';" + options.command; + if (options.idle) { + options.command = "echo '\033[2J\033[1;1H\033[01;34m[Idle]\033[0m'"; + } else if (options.command) { + options.command = "echo '\033[2J\033[1;1H';" + options.command + + ';printf "\033[01;30m\n\nProcess exited with code: $?\033[0m\n"'; + } - // \"`#" + name + "`\" if (options.command) { - args.push("-c", "nodosfilewarning=1;" + options.command - + "; ([ -e '" + watchFile + "' ] && " - + "rm '" + watchFile + "')"); + args.push("-c", "nodosfilewarning=1;" + options.command); var cmd = args[args.length - 1]; args[args.length - 1] = '"' + cmd.replace(/"/g, '\\"') + '"'; } - if (options.output) { - var path = watchFile[0] == "~" - ? join(process.env.HOME, watchFile.substr(1)) - : watchFile; - fs.writeFile(path, "-1", "utf8", run); - } - else - run(); + run(); function run(err){ if (err) return callback(err); @@ -2223,16 +2168,8 @@ module.exports = function setup(fsOptions) { function execFile(executablePath, options, callback) { if (isWin && execFileWin(executablePath, options, callback)) return; - if (options.hasOwnProperty('env')) { - options.env.__proto__ = fsOptions.defaultEnv; - } else { - options.env = fsOptions.defaultEnv; - } - if (options.cwd && options.cwd.charAt(0) == "~") - options.cwd = options.env.HOME + options.cwd.substr(1); - if (transformPath && options.cwd) - options.cwd = transformPath(options.cwd); + _setDefaultEnv(options); resolvePath(executablePath, { nocheck : 1, @@ -2259,10 +2196,9 @@ module.exports = function setup(fsOptions) { function execFileWin(executablePath, options, callback) { if (executablePath == "kill") { var pid = options.args && options.args[0]; - console.error(pid, sessions) + Object.keys(sessions).some(function(key) { if (sessions[key].pid == pid && sessions[key].pty) { - console.error(sessions[key].pty.pid) sessions[key].pty.killtree(-1); return true; } @@ -2271,6 +2207,25 @@ module.exports = function setup(fsOptions) { return true; } } + + function _setDefaultEnv(options) { + if (options.hasOwnProperty("env")) + options.env.__proto__ = fsOptions.defaultEnv; + else + options.env = fsOptions.defaultEnv; + + // Pty is only reading from the object itself; + var env = {}; + for (var prop in options.env) + env[prop] = options.env[prop]; + options.env = env; + + if (options.cwd && options.cwd.charAt(0) == "~") + options.cwd = options.env.HOME + options.cwd.substr(1); + + if (transformPath && options.cwd) + options.cwd = transformPath(options.cwd); + } function killtree(pid, options, callback) { var code = options.code || options.graceful ? "SIGTERM" : "SIGKILL"; diff --git a/node_modules/vfs-socket/consumer.js b/node_modules/vfs-socket/consumer.js index 3646fdc1..3b4efd2b 100644 --- a/node_modules/vfs-socket/consumer.js +++ b/node_modules/vfs-socket/consumer.js @@ -273,15 +273,18 @@ function Consumer() { function onExit(pid, code, signal) { var process = proxyProcesses[pid]; if (!process) return; - // TODO: not delete proxy if close is going to be called later. - // but somehow do delete proxy if close won't be called later. - delete proxyProcesses[pid]; + // TODO: how can we confirm that both close and exit are always called + if (process.closed) + delete proxyProcesses[pid]; + process.exited = true; process.emit("exit", code, signal); } function onProcessClose(pid) { var process = proxyProcesses[pid]; if (!process) return; - delete proxyProcesses[pid]; + if (process.exited) + delete proxyProcesses[pid]; + process.closed = true; process.emit("close"); } function onPtyKill(pid){ diff --git a/package.json b/package.json index 83db5c36..52828e84 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "c9.ide.find": "#35379124ca", "c9.ide.find.infiles": "#c132ad243c", "c9.ide.find.replace": "#44772dd796", - "c9.ide.run.debug": "#3b76105b4e", + "c9.ide.run.debug": "#a6bc1f422f", "c9.automate": "#47e2c429c9", "c9.ide.ace.emmet": "#6dc4585e02", "c9.ide.ace.gotoline": "#a8ff07c8f4", @@ -102,7 +102,7 @@ "c9.ide.recentfiles": "#7c099abf40", "c9.ide.remote": "#301d2ab519", "c9.ide.processlist": "#2b12cd1bdd", - "c9.ide.run": "#f360984649", + "c9.ide.run": "#4d73c19553", "c9.ide.run.build": "#0598fff697", "c9.ide.run.debug.xdebug": "#61dcbd0180", "c9.ide.save": "#4a4a60a004", diff --git a/plugins/c9.ide.terminal/predict_echo_test.js b/plugins/c9.ide.terminal/predict_echo_test.js index 335d84f7..b3f1e36d 100644 --- a/plugins/c9.ide.terminal/predict_echo_test.js +++ b/plugins/c9.ide.terminal/predict_echo_test.js @@ -100,12 +100,6 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse return tab.pane.aml.getPage("editor::" + tab.editorType).$ext; }); - predictor.on("mispredict", function(e) { - console.error("MISPREDICTED", e) - delete e.session; - throw new Error("MISPREDICTED: " + JSON.stringify(e)); - }); - describe('terminal.predict_echo', function() { this.timeout(30000); @@ -140,18 +134,28 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse session = editor.ace.getSession().c9session; send = session.send; - if (peek(-2) === "$") // maybe there already was a prompt - return init(); - afterPrompt(function() { setTimeout(init); }); + setTimeout(init); function init() { + afterPrompt(function() { setTimeout(start); }); // Make sure we have a prompt with a dollar for tests - afterPrompt(function() { done() }); - editor.ace.onTextInput("PS1='. $ '\n"); - //editor.ace.onTextInput("ssh lennart\n"); - //editor.ace.onTextInput("ssh ubuntu@ci.c9.io\n"); + // And terminal won't send rename commands in the middle of the test + // TODO: do we need to handle rename sequence in predict_echo instead? + editor.ace.onTextInput("PS1='. $ ';" + + "tmux setw automatic-rename off;" + + "printf '\\x1b]0;predict echo\\x07'\n"); + // editor.ace.onTextInput("ssh lennart\n"); + // editor.ace.onTextInput("ssh ubuntu@ci.c9.io\n"); } }); + function start() { + predictor.on("mispredict", function(e) { + console.error("MISPREDICTED", e) + delete e.session; + throw new Error("MISPREDICTED: " + JSON.stringify(e)); + }); + setTimeout(done) + } } function peek(offset) { @@ -528,7 +532,7 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root", "ace/test/asse afterPredict(":", function() { afterPrompt(done); send("\r"); - }) + }); sendAll("echo".split(""), function() { send(INPUT_BACKSPACE); send(INPUT_BACKSPACE); diff --git a/plugins/c9.ide.terminal/terminal.js b/plugins/c9.ide.terminal/terminal.js index 29cbc884..f1cf0408 100644 --- a/plugins/c9.ide.terminal/terminal.js +++ b/plugins/c9.ide.terminal/terminal.js @@ -668,6 +668,7 @@ define(function(require, exports, module) { var queue = ""; var warned = false; var timer = null; + var initialConnect = true; function send(data) { if (!(c9.status & c9.NETWORK)) @@ -680,7 +681,7 @@ define(function(require, exports, module) { timer = setTimeout(function() { timer = null; if (!session.connected) - return warnConnection(); + return initialConnect || warnConnection(); // Send data to stdin of tmux process session.pty.write(queue); queue = ""; @@ -746,6 +747,11 @@ define(function(require, exports, module) { tab: session.tab }); loadHistory(session); + initialConnect = false; + if (queue) { + session.pty.write(queue); + queue = ""; + } } }); }); diff --git a/plugins/c9.ide.terminal/tmux_connection.js b/plugins/c9.ide.terminal/tmux_connection.js index 6c0da3ff..6eba2527 100644 --- a/plugins/c9.ide.terminal/tmux_connection.js +++ b/plugins/c9.ide.terminal/tmux_connection.js @@ -77,13 +77,18 @@ module.exports = function(c9, proc, installPath, shell) { meta.process.stderr.on("data", function(data) { errBuffer += data.toString(); }); - meta.process.on("exit", function() { + meta.process.on("close", function() { if (!buffer && !errBuffer && options.retries < 4) { // tmux doesn't produce any output if two instances are invoked at the same time return setTimeout(function() { getOutputHistory(options, cb); }, options.retries * 100 + 300); } + if (buffer) { + var i = buffer.search(/\x1b\[1mPane is dead\x1b\[0m\s*$/); + if (i != -1) + buffer = buffer.slice(0, i).replace(/\s*$/, "\n"); + } cb(errBuffer, buffer); }); });