2015-02-10 19:41:24 +00:00
|
|
|
/**
|
|
|
|
* Terminal for the Cloud9
|
|
|
|
*
|
2015-02-15 02:12:39 +00:00
|
|
|
* @copyright 2013, Ajax.org B.V.
|
2015-02-10 19:41:24 +00:00
|
|
|
*/
|
|
|
|
define(function(require, exports, module) {
|
|
|
|
|
|
|
|
module.exports = function(c9, proc, installPath, shell) {
|
|
|
|
function reconnectTimed(session, force) {
|
|
|
|
if (session.killed) { // Session has been killed
|
|
|
|
console.warn("Reconnecting while session has been killed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(function(){
|
|
|
|
reconnect(session, force);
|
|
|
|
}, session.reconnecting === 0 ? 0 :
|
|
|
|
(session.reconnecting < 10 ? 100 : 1000));
|
|
|
|
}
|
|
|
|
|
|
|
|
function reconnect(session, force) {
|
|
|
|
// Make sure we have process control
|
|
|
|
if (!c9.has(c9.PROCESS)) {
|
|
|
|
console.warn("Reconnecting while not online");
|
|
|
|
// We'll let the stateChange handler retry
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((session.connecting || session.connected) && !force) {
|
|
|
|
console.error("Reconnecting while already connected/connecting. Abort.");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// A little counter for debugging purpose
|
|
|
|
if (!session.reconnecting)
|
|
|
|
session.reconnecting = 0;
|
|
|
|
session.reconnecting++;
|
|
|
|
|
|
|
|
// Make sure the pty is no longer active
|
|
|
|
session.disregard && session.disregard(true);
|
|
|
|
|
|
|
|
// Lets get our TMUX process
|
|
|
|
getTMUX(session, function(err) {
|
|
|
|
if (err) {
|
|
|
|
console.error("Error creating TMUX session: ", err.message);
|
|
|
|
|
|
|
|
if (err.code != "EACCESS")
|
|
|
|
reconnectTimed(session, true);
|
|
|
|
else
|
|
|
|
console.warn("Terminal EACCESS error");
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getOutputHistory(options, cb) {
|
|
|
|
options.id = options.id || this.id;
|
|
|
|
options.retries = (options.retries || 0) + 1;
|
|
|
|
var start = typeof options.start == "number" ? options.start : -32768;
|
|
|
|
var end = typeof options.end == "number" ? options.end : 1000;
|
|
|
|
var paneId = (options.id || this.id) + ":0.0"; // for now let's assume there is only one pane
|
|
|
|
|
|
|
|
if (c9.platform == "win32") return;
|
|
|
|
|
|
|
|
proc.tmux("", {
|
|
|
|
capturePane: {
|
|
|
|
start: start,
|
|
|
|
end: end,
|
|
|
|
pane: paneId.trim(),
|
|
|
|
joinLines: options.joinLines
|
|
|
|
}
|
|
|
|
}, function(err, _1, _2, meta) {
|
|
|
|
if (err || !meta.process) return cb(err);
|
|
|
|
var buffer = "", errBuffer = "";
|
|
|
|
meta.process.stdout.on("data", function(data) {
|
|
|
|
buffer += data.toString();
|
|
|
|
});
|
|
|
|
meta.process.stderr.on("data", function(data) {
|
|
|
|
errBuffer += data.toString();
|
|
|
|
});
|
2015-11-02 23:54:27 +00:00
|
|
|
meta.process.on("close", function() {
|
2015-02-10 19:41:24 +00:00
|
|
|
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);
|
|
|
|
}
|
2015-11-03 23:30:10 +00:00
|
|
|
if (buffer) {
|
|
|
|
var i = buffer.search(/\x1b\[1mPane is dead\x1b\[0m\s*$/);
|
|
|
|
if (i != -1)
|
|
|
|
buffer = buffer.slice(0, i).replace(/\s*$/, "\n");
|
|
|
|
}
|
2015-02-10 19:41:24 +00:00
|
|
|
cb(errBuffer, buffer);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function getStatus(options, cb) {
|
|
|
|
if (options.id == null)
|
|
|
|
options.id = this.id;
|
|
|
|
proc.tmux("", {
|
|
|
|
getStatus: options
|
|
|
|
}, function(err, _1, _2, meta) {
|
|
|
|
cb(err, meta.status);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendTmuxCommand(command, exec, terminal) {
|
|
|
|
if (c9.platform == "win32") return;
|
|
|
|
if (!terminal || !terminal.send)
|
|
|
|
terminal = this.terminal;
|
|
|
|
terminal.send("\u0002:" + command + (!exec ? "\r\u001b" : exec == 1 ? "\r" : ""));
|
|
|
|
}
|
|
|
|
|
|
|
|
function clearHistory() {
|
|
|
|
sendTmuxCommand("send-keys -R ; clear-history", 1, this.terminal);
|
|
|
|
}
|
|
|
|
|
|
|
|
function changeMode(){ return false; }
|
|
|
|
|
|
|
|
function getTMUX(session, callback) {
|
|
|
|
var disregarded;
|
|
|
|
|
|
|
|
session.getOutputHistory = getOutputHistory;
|
|
|
|
session.getStatus = getStatus;
|
|
|
|
session.sendTmuxCommand = sendTmuxCommand;
|
|
|
|
session.clearHistory = clearHistory;
|
|
|
|
|
|
|
|
var cwd = session.cwd || "";
|
|
|
|
if (!/^~(\/|$)/.test(cwd))
|
|
|
|
cwd = session.root + cwd;
|
|
|
|
var command = "";
|
|
|
|
var options = {
|
|
|
|
cwd: cwd,
|
|
|
|
cols: session.cols || 80,
|
|
|
|
rows: session.rows || 24,
|
|
|
|
name: "xterm-color",
|
|
|
|
base: installPath && installPath.replace(/^~/, c9.home || "~")
|
|
|
|
};
|
|
|
|
|
|
|
|
// Output Mode
|
|
|
|
if (session.output) {
|
|
|
|
options.idle = true;
|
|
|
|
options.session = session.id;
|
|
|
|
options.attach = true;
|
|
|
|
options.output = true;
|
|
|
|
options.detachOthers = !session.hasConnected;
|
|
|
|
}
|
|
|
|
// Terminal Mode
|
|
|
|
else {
|
|
|
|
command = shell || "";
|
|
|
|
|
|
|
|
if (!session.id) {
|
|
|
|
session.id = c9.workspaceId.split("/", 2).join("@")
|
|
|
|
+ "_" + Math.round(Math.random() * 1000);
|
|
|
|
options.attach = false;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
options.attach = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
options.session = session.id;
|
|
|
|
options.output = false;
|
|
|
|
options.terminal = true;
|
|
|
|
options.detachOthers = !session.hasConnected;
|
2015-05-01 01:53:12 +00:00
|
|
|
options.defaultEditor = session.defaultEditor;
|
2015-02-10 19:41:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Connect to backend and start tmux session
|
|
|
|
session.connecting = true;
|
|
|
|
session.connected = false;
|
|
|
|
session.setState("connecting");
|
|
|
|
|
|
|
|
session.reconnect = reconnect.bind(null, session);
|
|
|
|
|
|
|
|
proc.tmux(command, options, function(err, pty) {
|
|
|
|
if (session.pty && session.pty != pty) {
|
|
|
|
console.warn("trying to set pty twice", session.pty);
|
|
|
|
session.pty._events = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Document was unloaded before connection was made
|
|
|
|
if (!session.connecting) {
|
|
|
|
if (!err && !session.connected) {
|
|
|
|
session.pty = pty;
|
|
|
|
session.kill();
|
|
|
|
console.error("Got into weird terminal state.");
|
|
|
|
}
|
|
|
|
return callback(new Error("Session was not connecting"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle a possible error
|
|
|
|
if (err) {
|
|
|
|
console.warn("Error connecting to the terminal: ", err);
|
|
|
|
|
|
|
|
session.connecting = false;
|
|
|
|
session.setState("error");
|
|
|
|
|
|
|
|
return callback(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
session.pty = pty;
|
|
|
|
session.reconnecting = 0;
|
|
|
|
|
|
|
|
session.disregard = function(keepId) {
|
|
|
|
if (!keepId && !session.output)
|
|
|
|
delete session.id;
|
|
|
|
|
|
|
|
console.warn("Disregard terminal: ", session.id);
|
|
|
|
|
|
|
|
disregarded = true;
|
|
|
|
pty.kill();
|
|
|
|
delete session.pty;
|
|
|
|
};
|
|
|
|
|
|
|
|
session.pty.on("exit", function(){
|
|
|
|
if (!disregarded) {
|
|
|
|
session.connected = false;
|
|
|
|
session.connecting = false;
|
|
|
|
|
|
|
|
session.terminal.off("changeMode", changeMode);
|
|
|
|
if (session.monitor.wasQuitSent()) {
|
|
|
|
session.tab.meta.$ignore = true;
|
|
|
|
session.tab.close();
|
|
|
|
}
|
|
|
|
else if (session.tab.isActive() && session.tab.pane.visible
|
|
|
|
&& document.hasFocus()
|
|
|
|
) {
|
|
|
|
reconnect(session);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
session.pty.on("data", function(data) {
|
|
|
|
if (!disregarded) {
|
|
|
|
if (typeof data == "object") {
|
|
|
|
if (data.started)
|
2015-06-08 14:15:22 +00:00
|
|
|
return;
|
2015-02-10 19:41:24 +00:00
|
|
|
else if (data.code) {
|
2015-06-08 14:15:22 +00:00
|
|
|
if (data.type == "exception") { // Error
|
2015-02-10 19:41:24 +00:00
|
|
|
session.disregard();
|
|
|
|
console.error("Error creating TMUX session: ", err.message);
|
|
|
|
session.setState("error");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
session.warn(data);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return session.setSize(data);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (session.filter)
|
|
|
|
data = session.filter(data);
|
|
|
|
if (data)
|
|
|
|
session.write(data);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// first message from tmux clears screen discarding
|
|
|
|
session.terminal.once("changeMode", changeMode);
|
|
|
|
|
|
|
|
// Success
|
|
|
|
session.connecting = false;
|
|
|
|
session.connected = true;
|
|
|
|
session.hasConnected = true;
|
|
|
|
session.setState("connected");
|
|
|
|
|
|
|
|
// Resize the terminal to the size of the container
|
|
|
|
session.updatePtySize();
|
|
|
|
|
|
|
|
callback();
|
|
|
|
session.getEmitter()("connected");
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
init: function(session, cb) {
|
|
|
|
getTMUX(session, function(err) {
|
|
|
|
if (err) {
|
|
|
|
// util.alert(
|
|
|
|
// "Error opening Terminal",
|
|
|
|
// "Error opening Terminal",
|
|
|
|
// "Could not open terminal with the following reason:"
|
|
|
|
// + err);
|
|
|
|
console.error("Error opening Terminal: " + err.message);
|
|
|
|
|
|
|
|
session.setState("error");
|
|
|
|
|
|
|
|
if (err.code != "EACCESS")
|
|
|
|
reconnectTimed(session, true);
|
|
|
|
else
|
|
|
|
console.warn("Terminal EACCESS error");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Lets wait until we get process control back
|
|
|
|
c9.on("stateChange", function(e) {
|
|
|
|
if (e.state & c9.PROCESS && !session.connected) {
|
|
|
|
console.warn("Terminal reconnect due to connection regain");
|
|
|
|
reconnect(session);
|
|
|
|
}
|
|
|
|
}, session);
|
|
|
|
|
|
|
|
cb && cb(err, session);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
kill: function(session) {
|
|
|
|
console.warn("KILLING SESSION:", session.id);
|
|
|
|
|
|
|
|
if (session.id) {
|
|
|
|
proc.tmux("", {
|
|
|
|
session: session.id,
|
|
|
|
kill: true
|
|
|
|
}, function(err) {
|
|
|
|
// Ignore errors for now
|
|
|
|
if (err)
|
|
|
|
return console.error(err);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
console.warn("Killed tmux session: ", session.id);
|
|
|
|
|
|
|
|
// If we're still connecting disregard will not be set yet
|
|
|
|
// The callback of the connection process will check
|
|
|
|
// the connecting property and terminate the connection
|
|
|
|
// then.
|
|
|
|
if (session && session.disregard)
|
|
|
|
session.disregard();
|
|
|
|
|
|
|
|
delete session.id;
|
|
|
|
//doc.terminal.onResize(); ??
|
|
|
|
|
|
|
|
session.killed = true;
|
|
|
|
session.connecting = false;
|
|
|
|
session.connected = false;
|
|
|
|
session.setState("killed");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
});
|