c9-core/plugins/c9.ide.terminal/terminal.js

1354 wiersze
56 KiB
JavaScript
Czysty Zwykły widok Historia

2016-06-26 11:53:19 +00:00
define(function(require, exports, module) {
main.consumes = [
"Editor", "editors", "commands", "menus", "layout", "util",
"settings", "ui", "proc", "c9", "preferences", "tabManager",
"dialog.error", "dialog.question", "dialog.alert", "installer"
];
main.provides = ["terminal"];
return main;
function main(options, imports, register) {
var c9 = imports.c9;
var Editor = imports.Editor;
var editors = imports.editors;
var layout = imports.layout;
var proc = imports.proc;
var util = imports.util;
var ui = imports.ui;
var commands = imports.commands;
var prefs = imports.preferences;
var menus = imports.menus;
var tabs = imports.tabManager;
var settings = imports.settings;
var installer = imports.installer;
var question = imports["dialog.question"];
var showError = imports["dialog.error"].show;
var hideError = imports["dialog.error"].hide;
var alert = imports["dialog.alert"];
// Disabled: bad performance, openshift specific, possibly unreliable
// var Monitor = require("./monitor.js");
var markup = require("text!./terminal.xml");
var markupMenu = require("text!./menu.xml");
var Aceterm = require("./aceterm/aceterm");
var libterm = require("./aceterm/libterm");
// Needed to clear ace
var EditSession = require("ace/edit_session").EditSession;
var dummySession = new EditSession("");
var extensions = [];
// Set up the generic handle
var handle = editors.register("terminal", "Terminal",
Terminal, extensions);
var handleEmit = handle.getEmitter();
handleEmit.setMaxListeners(1000);
var TMUX = options.tmux || "~/.c9/bin/tmux";
var VFSROOT = options.root || "~";
var TMPDIR = options.tmpdir;
var tmuxConnection = require("./tmux_connection")(c9, proc,
options.installPath, options.shell);
var mnuTerminal;
var lastEditor;
var lastTerminal;
var shownDotsHelp;
var installPrompted;
var defaults = {
2017-01-30 11:32:54 +00:00
"flat-light": ["#eaf0f7", "#000000", "#bed1e3", false],
"flat-dark": ["#153649", "#FFFFFF", "#515D77", true],
"light": ["rgb(248, 248, 231)", "#000000", "rgb(137, 193, 253)", false],
"light-gray": ["rgb(248, 248, 231)", "#000000", "rgb(137, 193, 253)", false],
"dark": ["#153649", "#FFFFFF", "#515D77", true],
"dark-gray": ["#153649", "#FFFFFF", "#515D77", true]
2016-06-26 11:53:19 +00:00
};
var themeName;
if (options.defaults) {
for (themeName in options.defaults) {
defaults[themeName] = options.defaults[themeName];
}
}
// Import the CSS
ui.insertCss(require("text!./style.css"), options.staticPrefix, handle);
2017-01-30 11:32:54 +00:00
handle.on("load", function() {
2016-06-26 11:53:19 +00:00
commands.addCommand({
name: "openterminal",
group: "Terminal",
hint: "Opens a new terminal window",
msg: "opening terminal.",
bindKey: { mac: "Option-T", win: "Alt-T" },
exec: function (editor) {
var pane = tabs.focussedTab && tabs.focussedTab.pane;
if (tabs.getTabs(tabs.container).length === 0)
pane = null;
tabs.open({
editorType: "terminal",
focus: true,
pane: pane
2017-01-30 11:32:54 +00:00
}, function() {});
2016-06-26 11:53:19 +00:00
}
}, handle);
commands.addCommand({
name: "switchterminal",
group: "Terminal",
hint: "Switch between Editor and Terminal",
msg: "switching.",
bindKey: { mac: "Option-S", win: "Alt-S" },
isAvailable: function() {
return tabs.focussedTab;
},
exec: function (editor) {
if (!tabs.focussedTab)
return;
if (tabs.focussedTab.editorType !== "terminal")
lastTerminal && tabs.focusTab(lastTerminal);
else
lastEditor && tabs.focusTab(lastEditor);
}
}, handle);
commands.addCommand({
name: "clearterm",
group: "Terminal",
hint: "Clears the terminal buffer",
isAvailable: function(editor) {
return editor && editor.type == "terminal";
},
exec: function (editor) {
tabs.focussedTab.editor.clear();
}
}, handle);
var meta = '\x1b';
[
["close_term_pane", "x", "x"],
["split_term_pane", '"', '"'],
["layout_term_hor_even", "Meta-1", meta + "1"],
["layout_term_ver_even", "Meta-2", meta + "2"],
["layout_term_hor_main", "Meta-3", meta + "3"],
["layout_term_ver_main", "Meta-4", meta + "4"],
["move_term_paneup", "Up", '\x1b[A'],
["move_term_panedown", "Down", '\x1b[B'],
["move_term_paneright", "Right", '\x1b[C'],
["move_term_paneleft", "Left", '\x1b[D'],
["term_help", "?", '?'],
["term_restart", "", ":kill-server\r"],
["term_detach", "", ":detach -a\r"],
["toggle_term_status", "", ":set-option status on\r"]
].forEach(function(iter) {
commands.addCommand({
name: iter[0],
group: "Terminal",
bindKey: {
mac: "", //Ctrl-B " + iter[1].replace(/Meta/, "Command"),
win: "" //Ctrl-B " + iter[1]
},
isAvailable: function(editor, e) {
var type = editor && editor.type;
return (type == "terminal" || type == "output") && e.source == "click";
},
exec: function (editor) {
if (iter[0] == "toggle_term_status") {
var session = editor.activeDocument.getSession();
session.status = !(session.status || 0);
editor.write(String.fromCharCode(2)
+ iter[2].replace(/on\r/,
session.status ? "on\r" : "off\r"));
}
else {
editor.write(String.fromCharCode(2) + iter[2]);
}
}
}, handle);
});
var menu = tabs.getElement("mnuEditors");
var ctxItem = menus.addItemToMenu(menu,
new ui.item({
caption: "New Terminal",
2017-03-22 19:01:35 +00:00
hotkey: "commands.openterminal",
2016-06-26 11:53:19 +00:00
onclick: function(e) {
tabs.open({
active: true,
pane: this.parentNode.pane,
editorType: "terminal"
2017-01-30 11:32:54 +00:00
}, function() {});
2016-06-26 11:53:19 +00:00
}
}), 200, handle);
menus.addItemByPath("Window/New Terminal", new ui.item({
command: "openterminal"
}), 30, handle);
menus.addItemByPath("Window/Navigation/~", new ui.divider(), 1500, handle);
menus.addItemByPath("Window/Navigation/Switch Between Editor and Terminal", new ui.item({
command: "switchterminal"
}), 1550, handle);
2017-01-30 11:32:54 +00:00
function setSettings() {
2016-06-26 11:53:19 +00:00
libterm.cursorBlink = settings.getBool("user/terminal/@blinking");
libterm.scrollback =
settings.getNumber("user/terminal/@scrollback") || 1000;
var cname = ".c9terminal .c9terminalcontainer .terminal";
var sname = ".c9terminal .c9terminalcontainer";
var fsize = settings.getNumber("user/terminal/@fontsize");
var fstyle = settings.getBool("user/terminal/@antialiasedfonts");
var fcolor = settings.get("user/terminal/@foregroundColor");
var bcolor = settings.get("user/terminal/@backgroundColor");
var scolor = settings.get("user/terminal/@selectionColor");
[
[cname, "fontFamily", settings.get("user/terminal/@fontfamily")
|| "Ubuntu Mono, Menlo, Consolas, monospace"],
[cname, "fontSize", fsize ? fsize + "px" : "10px"],
[cname, "WebkitFontSmoothing", fstyle ? "antialiased" : "auto"],
[cname, "MozOSXFontSmoothing", fstyle ? "grayscale" : "auto"],
[cname, "color", fcolor || "rgb(255,255,255)"],
[sname, "backgroundColor", bcolor || "rgb(25, 34, 39)"],
[cname + " .ace_selection", "backgroundColor", scolor || "rgb(81, 93, 119)"]
].forEach(function(i) {
ui.setStyleRule(i[0], i[1], i[2]);
});
// Small hack until we have terminal themes
var colors;
if (bcolor == "#eaf0f7") {
colors = [
// dark:
'#eaf0f7', // background wrong link
'#cc0000',
'#4e9a06',
'#c4a000',
'#3465a4',
'#75507b',
'#06989a',
'#d3d7cf',
// bright:
'#555753', // grey
'#ef2929', // red
'#579818', // green
'#C3A613', // yellow
'#5183B8', // blue
'#ad7fa8', // purple
'#20C7C7', // mint
'#BBBBBB' // light grey
];
}
libterm.setColors(fcolor, bcolor, colors);
handleEmit("settingsUpdate");
}
// Terminal
settings.on("read", function(e) {
var skin = settings.get("user/general/@skin");
var colors = defaults[skin] || defaults["dark"];
settings.setDefaults("user/terminal", [
["backgroundColor", colors[0]],
["foregroundColor", colors[1]],
["selectionColor", colors[2]],
["antialiasedfonts", colors[3]],
["fontfamily", "Ubuntu Mono, Menlo, Consolas, monospace"], // Monaco,
["fontsize", "12"],
["blinking", "false"],
["scrollback", 1000]
]);
setSettings();
}, handle);
settings.on("user/terminal", setSettings);
layout.on("themeChange", function(e) {
setSettings();
2017-03-05 18:54:23 +00:00
var colors = defaults[e.oldTheme];
if (!colors) return;
if (!(settings.get("user/terminal/@backgroundColor") == colors[0] &&
settings.get("user/terminal/@foregroundColor") == colors[1] &&
settings.get("user/terminal/@selectionColor") == colors[2] &&
settings.get("user/terminal/@antialiasedfonts") == colors[3]))
2016-06-26 11:53:19 +00:00
return false;
});
layout.on("themeDefaults", function(e) {
2017-03-05 18:54:23 +00:00
var colors = defaults[e.theme];
if (!colors) return;
settings.set("user/terminal/@backgroundColor", colors[0]);
settings.set("user/terminal/@foregroundColor", colors[1]);
settings.set("user/terminal/@selectionColor", colors[2]);
settings.set("user/terminal/@antialiasedfonts", colors[3]);
2016-06-26 11:53:19 +00:00
}, handle);
// Settings UI
prefs.add({
2017-01-30 11:32:54 +00:00
"Editors": {
"Terminal": {
2016-06-26 11:53:19 +00:00
position: 100,
2017-01-30 11:32:54 +00:00
"Text Color": {
2016-06-26 11:53:19 +00:00
type: "colorbox",
path: "user/terminal/@foregroundColor",
position: 10100
},
2017-01-30 11:32:54 +00:00
"Background Color": {
2016-06-26 11:53:19 +00:00
type: "colorbox",
path: "user/terminal/@backgroundColor",
position: 10200
},
2017-01-30 11:32:54 +00:00
"Selection Color": {
2016-06-26 11:53:19 +00:00
type: "colorbox",
path: "user/terminal/@selectionColor",
position: 10250
},
2017-01-30 11:32:54 +00:00
"Font Family": {
2016-06-26 11:53:19 +00:00
type: "textbox",
path: "user/terminal/@fontfamily",
position: 10300
},
2017-01-30 11:32:54 +00:00
"Font Size": {
2016-06-26 11:53:19 +00:00
type: "spinner",
path: "user/terminal/@fontsize",
min: "1",
max: "72",
position: 11000
},
2017-01-30 11:32:54 +00:00
"Antialiased Fonts": {
2016-06-26 11:53:19 +00:00
type: "checkbox",
path: "user/terminal/@antialiasedfonts",
position: 12000
},
2017-01-30 11:32:54 +00:00
"Blinking Cursor": {
2016-06-26 11:53:19 +00:00
type: "checkbox",
path: "user/terminal/@blinking",
position: 12000
},
2017-01-30 11:32:54 +00:00
"Scrollback": {
2016-06-26 11:53:19 +00:00
type: "spinner",
path: "user/terminal/@scrollback",
min: "1",
max: "100000",
position: 13000
}
}
}
}, handle);
// Offline
c9.on("stateChange", function(e) {
// Online
if (e.state & c9.NETWORK) {
ctxItem && ctxItem.enable();
ui.setStyleRule(".terminal .ace_content", "opacity", "");
}
// Offline
else {
ctxItem && ctxItem.disable();
ui.setStyleRule(".terminal .ace_content", "opacity", "0.5");
}
});
});
2017-01-30 11:32:54 +00:00
handle.on("unload", function() {
2016-06-26 11:53:19 +00:00
mnuTerminal = null;
lastEditor = null;
lastTerminal = null;
shownDotsHelp = null;
installPrompted = null;
});
2017-01-30 11:32:54 +00:00
handle.draw = function() {
2016-06-26 11:53:19 +00:00
ui.insertMarkup(null, markupMenu, handle);
mnuTerminal = handle.getElement("mnuTerminal");
if (c9.platform == "win32") {
var nodes = mnuTerminal.childNodes;
while (nodes[6]) {
mnuTerminal.removeChild(nodes[6]);
}
}
2017-01-30 11:32:54 +00:00
handle.draw = function() {};
2016-06-26 11:53:19 +00:00
};
handle.Terminal = Terminal;
handle.VFSROOT = VFSROOT;
var counter = 0;
/***** Initialization *****/
function Terminal(isOutputTerminal) {
var plugin = new Editor("Ajax.org", main.consumes, extensions);
var emit = plugin.getEmitter();
var container, barTerminal, currentSession, currentDocument, aceterm;
plugin.on("draw", function(e) {
// Create UI elements
ui.insertMarkup(e.tab, markup, plugin);
barTerminal = plugin.getElement("barTerminal");
// Draw menu
handle.draw();
// Set context menu
barTerminal.setAttribute("contextmenu", mnuTerminal);
// Fetch Reference to the HTML Element
container = barTerminal.firstChild.$ext;
// todo do we need barTerminal or e.htmlNode
aceterm = Aceterm.createEditor(null, "ace/theme/idle_fingers");
aceterm.container.style.position = "absolute";
aceterm.container.style.left = "0px";
aceterm.container.style.right = "0px";
aceterm.container.style.top = "0px";
aceterm.container.style.bottom = "0px";
// e.htmlNode
container.appendChild(aceterm.container);
aceterm.on("focus", function() {
barTerminal.setAttribute("class", "c9terminal c9terminalFocus");
});
aceterm.on("blur", function() {
barTerminal.setAttribute("class", "c9terminal");
});
2017-01-30 11:32:54 +00:00
handle.on("settingsUpdate", function() {
2016-06-26 11:53:19 +00:00
aceterm.renderer.updateFull();
}, plugin);
var cm = commands;
// TODO find better way for terminal and ace commands to coexist
aceterm.commands.addCommands(cm.getExceptionList());
cm.on("update", function() {
aceterm.commands.addCommands(cm.getExceptionList());
}, plugin);
aceterm.commands.exec = function(command) {
return cm.exec(command);
};
2017-01-30 11:32:54 +00:00
plugin.on("unload", function() {
2016-06-26 11:53:19 +00:00
aceterm.destroy();
container.innerHTML = "";
aceterm = null;
container = null;
});
2017-01-30 11:32:54 +00:00
aceterm.on("focus", function() {
2016-06-26 11:53:19 +00:00
if (currentSession && !currentSession.connected && currentSession.reconnect)
currentSession.reconnect();
});
handleEmit.sticky("create", { editor: plugin }, plugin);
});
/***** Methods *****/
function write(data) {
if (currentSession) {
if (currentSession.connected)
currentSession.pty.write(data);
else {
var session = currentSession;
plugin.on("connect", function wait(e) {
if (e.tab == session.tab) {
currentSession.pty.write(data);
plugin.off("connect", wait);
}
});
}
}
}
2017-01-30 11:32:54 +00:00
function focus() {
2016-06-26 11:53:19 +00:00
if (aceterm)
aceterm.focus();
}
2017-01-30 11:32:54 +00:00
function blur() {
2016-06-26 11:53:19 +00:00
// var cursor = barTerminal.$ext.querySelector(".terminal .reverse-video");
// if (cursor && settings.getBool("user/terminal/blinking"))
// cursor.parentNode.removeChild(cursor);
barTerminal.setAttribute("class", "c9terminal");
if (aceterm)
aceterm.blur();
}
var afterAnim;
function resize(e) {
var renderer = aceterm && aceterm.renderer;
if (!renderer || !currentDocument) return;
if (e.type == "anim") {
var htmlNode = aceterm.container;
if (!htmlNode)
return;
if (e.vertical) {
var size = e.current === 0
? Math.abs(e.delta) - 5
- currentDocument.tab.pane.aml.$buttons.offsetHeight
: htmlNode.offsetHeight + e.delta;
renderer.onResize(false, null, null, size);
}
else {
renderer.onResize(false, null,
htmlNode.offsetWidth + e.delta);
}
afterAnim = true;
}
else if (e.type == "afteranim" && afterAnim) {
afterAnim = false;
} else {
afterAnim = false;
renderer.$updateSizeAsync();
}
}
function updateCover(aceSession, add) {
if (aceSession.updatingStatus) return;
if (!add) {
if (!aceSession.term.tmuxDotCover) return;
var x = aceSession.term.tmuxDotCover.width;
var y = aceSession.term.tmuxDotCover.height;
if (isEmpty(aceSession.term, x, y)) return;
}
aceSession.updatingStatus = true;
2017-01-30 11:32:54 +00:00
aceSession.c9session.getStatus({ clients: true }, function(e, s) {
2016-06-26 11:53:19 +00:00
aceSession.updatingStatus = false;
if (e) return console.warn(e);
var term = aceSession.term;
add = term.rows > s.height || term.cols > s.width;
setCover(aceSession, add);
if (aceSession.term.tmuxDotCover) {
aceSession.term.tmuxDotCover.width = s.width;
aceSession.term.tmuxDotCover.height = s.height;
aceSession._signal("changeFrontMarker");
}
});
}
function showTmuxDotsHelp(e) {
if (settings.getBool("user/terminal/@collab") || shownDotsHelp)
return;
var aceSession = e && e.editor && e.editor.session.term;
if (aceSession && !aceSession.tmuxDotCover)
return;
shownDotsHelp = true;
alert.show("Collaborative Terminal",
"Your Terminal is in Collaborative mode",
"When you Share your workspace with others, they can use your "
+ "Terminal as well.\n\n"
+ "Some side-effects are that the terminal view will only be as "
+ "large as the smallest view of all collaborators. The rest "
+ "of the space is unavailable. Also, you cant scroll back "
+ "the history. \n\n"
+ "(note that this can also happen when the workspace is open "
+ "in another window; to resolve this, right click on the "
+ "terminal and choose detach other clients)",
2017-01-30 11:32:54 +00:00
function() { // Hide
2016-06-26 11:53:19 +00:00
settings.set("user/terminal/@collab", alert.dontShow);
}, {
showDontShow: true
});
}
function isEmpty(term, x, y) {
if (x >= term.cols || y >= term.rows) return false;
var lines = term.lines;
for (var i = term.ybase; i < lines.length; i++) {
if (!lines[i]) continue;
for (var j = term.ybase; j < lines.length; j++) {
if (lines[i][j] && lines[i][j][1])
return false;
}
}
return true;
}
function setCover(aceSession, add) {
if (Boolean(aceSession.term.tmuxDotCover) === Boolean(add))
return;
aceSession.setScrollTop(Number.MAX_VALUE);
if (aceSession.term.tmuxDotCover) {
aceSession.removeMarker(aceSession.term.tmuxDotCover.id);
aceSession.term.tmuxDotCover = null;
return;
}
var marker = {};
marker.update = function(html, markerLayer, session, config) {
if (!this.width || !this.height || !session.term) return;
var rows = session.term.rows;
var cols = session.term.cols;
var coverHeight = config.lineHeight * (rows - this.height);
var screenBottom = config.height - coverHeight + config.offset + 2;
var screenRight = (cols - this.width) * config.characterWidth;
2017-01-30 11:32:54 +00:00
html.push("<div style='height:", coverHeight, "px;", "left:0; right: ", screenRight, "px; top:", screenBottom, "px;' ",
2016-06-26 11:53:19 +00:00
"class='c9terminalcontainer cover bottom'></div>",
"<div style='width:", screenRight, "px; height: ", screenBottom, "px; top:0; right:0;' ",
"class='c9terminalcontainer cover right'></div>",
"<div style='width:", screenRight, "px; bottom:0; top:", screenBottom, "px; right:0;' ",
"class='c9terminalcontainer cover'></div>"
);
};
aceSession.addDynamicMarker(marker, true);
aceSession.term.tmuxDotCover = marker;
if (aceSession.ace)
aceSession.ace.on("click", showTmuxDotsHelp);
}
2017-01-30 11:32:54 +00:00
function isTmuxBorderChar(x) { return !x || x[1] && "\xb7\u2500\u2502\u2518".indexOf(x[1]) != -1; }
2016-06-26 11:53:19 +00:00
function clearTmuxBorders(terminal) {
var trimmed = false;
var lines = terminal.lines;
for (var i = Math.max(0, terminal.ybase - 4); i < lines.length; i++) {
var line = terminal.lines[i];
if (line) {
var col = Math.min(line.length, terminal.cols) - 1;
while (col >= 0 && isTmuxBorderChar(line[col])) {
if (line[col]) line[col][1] = '';
col--;
trimmed = true;
}
}
}
updateCover(terminal.aceSession, trimmed);
}
function loadHistory(session) {
if (session.terminal.tmuxDotCover)
return;
session.getOutputHistory({}, function(e, data) {
if (!e && data) {
session.terminal.setOutputHistory(data, true);
2017-01-30 11:32:54 +00:00
session.getStatus({ clients: true }, function(e, status) {
2016-06-26 11:53:19 +00:00
if (e || !status) return;
if (status.clients && status.clients.length > 0) {
var terminal = session.terminal;
var rows = terminal.rows;
var cols = terminal.cols;
session.terminal.resize(status.width, status.height);
updateCover(session.terminal.aceSession);
terminal.cols = cols;
while (terminal.rows < rows) {
terminal.lines.push(terminal.blankLine());
terminal.rows++;
}
terminal.rows = rows;
}
});
}
});
}
function createTerminal(session, state) {
var queue = "";
var warned = false;
var timer = null;
var initialConnect = true;
function send(data) {
if (!(c9.status & c9.NETWORK))
return warnConnection();
emit("input", { data: data, session: session });
queue += data;
if (!timer) {
timer = setTimeout(function() {
timer = null;
if (!session.connected)
return initialConnect || warnConnection();
// Send data to stdin of tmux process
session.pty.write(queue);
queue = "";
}, 1);
}
}
function warnConnection() {
if (warned)
return;
warned = true;
var error = showError("Terminal was disconnected. Trying to reconnect");
if (!session.connecting && session.reconnect)
session.reconnect();
session.once("connected", function() {
hideError(error);
warned = false;
});
}
// Create the terminal renderer and monitor
var terminal = new Aceterm(0, 0, send);
session.terminal = terminal;
session.monitor = terminal.monitor;
session.aceSession = terminal.aceSession;
session.aceSession.c9session = session;
session.send = send;
// Add method to write to terminal
session.write = function(data) {
var handled = emit("beforeWrite", { data: data, session: session });
if (!handled)
session.terminal.write(data);
emit("afterWrite", { data: data, session: session });
};
// Create a container and initialize the terminal in it.
session.attach();
// Update the terminal title
terminal.on("title", function(title) {
emit("title", { title: title });
if (!session.output) {
session.doc.title =
session.doc.tooltip = title.replace(/^.+?:\d+:/, "");
}
});
session.aceSession.resize = session.resize.bind(session);
// delay a little until we have correct size
2017-01-30 11:32:54 +00:00
aceterm.renderer.once("afterRender", function start() {
2016-06-26 11:53:19 +00:00
if (session.resize() === false)
return aceterm.renderer.once("afterRender", start);
// Lets get our TMUX process
tmuxConnection.init(session, function(err, session) {
if (err)
emit("connectError", { error: err });
else {
emit("connect", {
id: session.id,
tab: session.tab
});
loadHistory(session);
initialConnect = false;
if (queue) {
session.pty.write(queue);
queue = "";
}
}
});
});
// hack to deal with dotted borders drawn by tmux
terminal.on("afterWrite", function() {
clearTmuxBorders(terminal);
});
session.getEmitter().sticky("terminalReady", session);
}
/***** Lifecycle *****/
plugin.on("documentLoad", function(e) {
var doc = e.doc;
var session = doc.getSession();
2017-01-30 11:32:54 +00:00
session.__defineGetter__("tab", function() { return doc.tab; });
session.__defineGetter__("doc", function() { return doc; });
session.__defineGetter__("defaultEditor", function() {
2016-06-26 11:53:19 +00:00
return settings.getBool("user/terminal/@defaultEnvEditor");
});
2017-01-30 11:32:54 +00:00
session.attach = function() {
2016-06-26 11:53:19 +00:00
if (session.aceSession && aceterm) {
aceterm.setSession(session.aceSession);
aceterm.container.style.display = "block";
}
else
aceterm.container.style.display = "none";
};
2017-01-30 11:32:54 +00:00
session.detach = function() {
2016-06-26 11:53:19 +00:00
// if (session.aceSession)
// aceterm.setSession(session.aceSession);
};
2017-01-30 11:32:54 +00:00
session.kill = function() {
2016-06-26 11:53:19 +00:00
tmuxConnection.kill(this);
};
2017-01-30 11:32:54 +00:00
session.warn = function(err) {
2016-06-26 11:53:19 +00:00
if (err.code == "EINSTALL" && !installPrompted) {
installPrompted = true;
question.show("Wrong version of dependencies installed",
err.message,
"Cloud9 detected you have unsupported version of a dependency "
+ "installed. Would you like to open the installer "
+ "to update to the latest version?",
2017-01-30 11:32:54 +00:00
function() { // Yes
2016-06-26 11:53:19 +00:00
installer.reinstall("Cloud9 IDE");
},
2017-01-30 11:32:54 +00:00
function() { // No
2016-06-26 11:53:19 +00:00
// Do nothing
},
{
yes: "Update",
no: "Not now",
});
}
2017-01-30 11:32:54 +00:00
};
2016-06-26 11:53:19 +00:00
session.setState = function(state) {
if (!plugin.loaded)
return;
if (session == currentSession) {
var el = container.querySelector(".ace_content");
el.style.opacity = state == "connected" ? "" : "0.5";
}
if (state == "connecting") {
session.tab.classList.add("connecting");
}
else if (state == "error" || state == "killed") {
doc.tab.classList.add("error");
}
else {
doc.tab.classList.remove("error");
session.tab.classList.remove("connecting");
}
};
var sizeChanged = null;
var waitForServer = null;
session.setSize = function(size) {
if (size) {
clearTimeout(waitForServer);
waitForServer = null;
var term = this.terminal;
term.setSize(size.cols, size.rows);
term.$resizeMessageT = Date.now();
term.$resizeDelay = (term.$resizeMessageT - term.$resizeMessageSentT) || 0;
if (sizeChanged) {
sizeChanged = false;
this.updatePtySize();
}
}
};
session.updatePtySize = function() {
// todo check tab.visible
if (this.pty && this.cols > 1 && this.rows > 1 && !waitForServer) {
this.terminal.$resizeMessageSentT = Date.now();
this.pty.resize(this.cols, this.rows);
clearTimeout(waitForServer);
waitForServer = setTimeout(function() {
waitForServer = null;
}, 1000);
} else
sizeChanged = true;
};
session.resize = function(force) {
if (!this.aceSession) return;
var terminal = this.terminal;
var ace = this.aceSession.ace;
if (!terminal || !ace) return;
var size = ace.renderer.$size;
var config = ace.renderer.layerConfig;
var h = size.scrollerHeight;
var w = size.scrollerWidth - 2 * config.padding;
if (!h || config.lineHeight <= 1)
return false;
// top 1px is for cursor outline
var rows = Math.floor((h - 1) / config.lineHeight);
if (rows <= 2 && !ace.renderer.scrollBarV.isVisible)
w -= ace.renderer.scrollBarV.width;
var cols = Math.floor(w / config.characterWidth);
if (!cols || !rows)
return;
// Don't do anything if the size remains the same
if (!force && cols == terminal.cols && rows == terminal.rows)
return;
// do not resize terminal to very small heights during initialization
rows = Math.max(rows, 2);
cols = Math.max(cols, 2);
if (cols > 1000 || rows > 1000) {
console.error("invalid terminal size");
return;
}
terminal.resize(cols, rows);
session.cols = cols;
session.rows = rows;
this.updatePtySize();
};
2017-01-30 11:32:54 +00:00
function setTabColor() {
2016-06-26 11:53:19 +00:00
var bg = settings.get("user/terminal/@backgroundColor");
var shade = util.shadeColor(bg, 0.75);
var skinName = settings.get("user/general/@skin");
var isLight = ~skinName.indexOf("flat") || shade.isLight;
doc.tab.backgroundColor = isLight ? bg : shade.color;
if (isLight) {
if (~skinName.indexOf("flat") && !shade.isLight) {
doc.tab.classList.add("dark");
container.className = "c9terminalcontainer flat-dark";
}
else {
doc.tab.classList.remove("dark");
container.className = "c9terminalcontainer";
}
}
else {
doc.tab.classList.add("dark");
container.className = "c9terminalcontainer dark";
}
}
if (!isOutputTerminal)
setTabColor();
// Prevent existing session from being reset
if (session.terminal) {
if (session.connecting)
session.tab.classList.add("connecting");
return;
}
// Set id of previous session if applicable
session.id = e.state && e.state.id || session.id
|| isOutputTerminal && "output";
session.root = VFSROOT;
session.cwd = e.state.cwd || handleEmit("setTerminalCwd") || "";
session.output = isOutputTerminal;
// When document gets unloaded everything should be cleaned up
2017-01-30 11:32:54 +00:00
doc.on("unload", function() {
2016-06-26 11:53:19 +00:00
// Stop the shell process at the remote machine
if (!options.testing)
session.kill();
// Destroy the terminal
if (session.terminal)
session.terminal.destroy();
}, doc);
doc.on("setTitle", function(e) {
if (session.mnuItem)
session.mnuItem.setAttribute("caption", e.title);
}, doc);
if (isOutputTerminal) {
2017-01-30 11:32:54 +00:00
session.connect = function() {
session.connect = function() {};
2016-06-26 11:53:19 +00:00
// Connect to a new or attach to an existing tmux session
createTerminal(session, e.state);
// Resize
session.resize();
};
return;
}
var tab = doc.tab;
2017-01-30 11:32:54 +00:00
tab.on("beforeClose", function() {
2016-06-26 11:53:19 +00:00
if (!settings.getBool("user/terminal/noclosequestion")
&& !tab.meta.$ignore && !options.testing) {
question.show("Close Terminal?",
"Are you sure you want to close this terminal?",
"Closing this terminal will stop any processes that it hosts.",
2017-01-30 11:32:54 +00:00
function() { // Yes
2016-06-26 11:53:19 +00:00
tab.meta.$ignore = true;
tab.close();
if (question.dontAsk)
settings.set("user/terminal/noclosequestion", "true");
},
2017-01-30 11:32:54 +00:00
function() { // No
2016-06-26 11:53:19 +00:00
// do nothing; allow user to continue
if (question.dontAsk)
settings.set("user/terminal/noclosequestion", "true");
},
{ showDontAsk: true, yes: "Close", no: "Cancel" });
return false;
}
}, session);
handle.on("settingsUpdate", setTabColor, doc);
// Some terminals won't set the title, lets set a default
if (!doc.title)
doc.title = "Terminal";
// Connect to a new or attach to an existing tmux session
createTerminal(session, e.state);
});
plugin.on("documentActivate", function(e) {
// Remove the previously visible terminal
if (currentSession)
currentSession.detach();
// Set the current terminal as visible terminal
currentDocument = e.doc;
currentSession = e.doc.getSession();
currentSession.attach();
currentSession.resize();
var el = container.querySelector(".ace_content");
el.style.transition = "opacity 150ms";
el.style.transitionDelay = "50ms";
el.style.opacity = currentSession.connected ? "" : "0.5";
// Focus
// plugin.focus();
});
plugin.on("documentUnload", function(e) {
var session = e.doc.getSession();
// Remove the element from the container
session.detach();
// Clear current session
if (currentSession == session) {
currentSession = null;
currentDocument = null;
aceterm && aceterm.setSession(dummySession);
}
});
plugin.on("getState", function(e) {
var session = e.doc.getSession();
if (!session.id)
return;
e.state.id = session.id;
e.state.cwd = session.cwd;
e.state.width = barTerminal.lastWidth || barTerminal.getWidth();
e.state.height = barTerminal.lastHeight || barTerminal.getHeight();
// @todo scrollback log
if (!e.filter && session.aceSession) {
var aceSession = session.aceSession;
e.state.scrollTop = aceSession.getScrollTop();
if (!aceSession.selection.isEmpty() || aceSession.selection.rangeCount > 1)
e.state.selection = aceSession.selection.toJSON();
}
});
plugin.on("setState", function(e) {
var session = e.doc.getSession();
session.id = e.state.id;
session.cwd = e.state.cwd;
// @todo scrollback log
var aceSession = session.aceSession;
if (aceSession) {
if (e.state.scrollTop)
aceSession.setScrollTop(e.state.scrollTop);
if (e.state.selection)
aceSession.selection.fromJSON(e.state.selection);
}
});
2017-01-30 11:32:54 +00:00
plugin.on("clear", function() {
2016-06-26 11:53:19 +00:00
if (currentSession) {
var t = currentSession.terminal;
if (!t) return;
t.ybase = 0;
t.lines = t.lines.slice(-(t.ybase + t.rows));
}
});
plugin.on("copy", function(e) {
if (e.native) return; // Ace handles this herself
var data = aceterm.getCopyText();
e.clipboardData.setData("text/plain", data);
});
plugin.on("paste", function(e) {
if (e.native) return; // Ace handles this herself
var data = e.clipboardData.getData("text/plain");
if (data !== false)
aceterm.onPaste(data);
});
plugin.on("focus", function(e) {
if (e.lost) blur();
else focus();
});
tabs.on("focus", function(e) {
if (e.tab.editorType === "terminal")
lastTerminal = e.tab;
if (e.tab.editorType === "ace")
lastEditor = e.tab;
});
tabs.on("tabAfterClose", function(e) {
if (e.tab === lastTerminal)
lastTerminal = null;
if (e.tab === lastEditor)
lastEditor = null;
});
2017-01-30 11:32:54 +00:00
plugin.on("blur", function() {
2016-06-26 11:53:19 +00:00
blur();
});
plugin.on("resize", function(e) {
resize(e);
});
2017-01-30 11:32:54 +00:00
plugin.on("enable", function() {
2016-06-26 11:53:19 +00:00
});
2017-01-30 11:32:54 +00:00
plugin.on("disable", function() {
2016-06-26 11:53:19 +00:00
});
2017-01-30 11:32:54 +00:00
plugin.on("unload", function() {
2016-06-26 11:53:19 +00:00
});
/***** Register and define API *****/
if (isOutputTerminal)
plugin.freezePublicAPI.baseclass();
/**
* The output handle, responsible for events that involve all
* output instances. This is the object you get when you request
* the output service in your plugin.
*
* Example:
*
* define(function(require, exports, module) {
* main.consumes = ["output"];
* main.provides = ["myplugin"];
* return main;
*
* function main(options, imports, register) {
* var outputHandle = imports.output;
* });
* });
*
*
* @class output
* @extends Plugin
* @singleton
*/
/**
* The terminal handle, responsible for events that involve all
* terminal instances. This is the object you get when you request
* the terminal service in your plugin.
*
* Example:
*
* define(function(require, exports, module) {
* main.consumes = ["terminal"];
* main.provides = ["myplugin"];
* return main;
*
* function main(options, imports, register) {
* var terminalHandle = imports.terminal;
* });
* });
*
*
* @class terminal
* @extends Plugin
* @singleton
*/
/**
* Output Editor for Cloud9. This editor does not allow
* editing content. Instead it displays the output of a PTY in the
* workspace. This editor is similar to terminal, except that it
* doesn't start the default, instead it connects to an existing
* TMUX session in which a process can be started using the
* {@link run#run run} plugin.
*
* Example of instantiating a new output pane:
*
* tabManager.open({
* editorType : "output",
* active : true,
* document : {
* title : "My Process Name",
* output : {
* id : "name_of_process"
* }
* }
* }, function(){});
*
* @class output.Output
* @extends Terminal
*/
/**
* The type of editor. Use this to create the output using
* {@link tabManager#openEditor} or {@link editors#createEditor}.
* @property {"output"} type
* @readonly
*/
/**
* Terminal Editor for Cloud9. This editor does not allow
* editing content. Instead it displays the output of a PTY in the
* workspace.
*
* Example of instantiating a new terminal:
*
* tabManager.openEditor("terminal", true, function(err, tab) {
* if (err) throw err;
*
* var terminal = tab.editor;
* terminal.write("ls\n");
* });
*
* @class terminal.Terminal
* @extends Editor
*/
/**
* The type of editor. Use this to create the terminal using
* {@link tabManager#openEditor} or {@link editors#createEditor}.
* @property {"terminal"} type
* @readonly
*/
/**
* Retrieves the state of a document in relation to this editor
* @param {Document} doc the document for which to return the state
* @method getState
* @return {Object}
* @return {String} return.id The unique id of the terminal session.
* @return {Number} return.width The width of the terminal in pixels.
* @return {Number} return.height The height of the terminal in pixels.
* @return {Number} return.scrollTop The amount of pixels scrolled.
* @return {Object} return.selection Describing the current state
* of the selection. This can become a complex object when
* there are multiple selections.
*/
plugin.freezePublicAPI({
/**
* Reference to the ace instance used by this terminal for
* rendering the output of the terminal.
* @property {Ace.Editor} ace
* @readonly
*/
2017-01-30 11:32:54 +00:00
get ace() { return aceterm; },
2016-06-26 11:53:19 +00:00
/**
* The HTMLElement containing the termainl.
* @property {HTMLElement} container
* @readonly
*/
2017-01-30 11:32:54 +00:00
get container() { return container; },
2016-06-26 11:53:19 +00:00
_events: [
/**
* Fires when a connection attempt has failed
* @event connectError
* @param {Object} e
* @param {Error} e.error describes the error that has occured.
*/
"connectError",
/**
* Fires when a connection with the PTY on the server is
* established. From this moment on data can be received
* by the terminal and data can be written to the terminal.
* @event connect
* @param {Object} e
* @param {Tab} e.tab the tab of the terminal that got connected
* @param {String} e.id the session id of the terminal
*/
"connect"
],
/**
* @ignore This is here to overwrite default behavior
*/
2017-01-30 11:32:54 +00:00
isClipboardAvailable: function(e) { return !e.fromKeyboard; },
2016-06-26 11:53:19 +00:00
/**
* Writes a string to the terminal. The message is send to the
* server and interpreted as if it was typed by the user. You
* can send modifier keys by using their hex representation.
* @param {String} message the message to write to the terminal.
*/
write: write,
2016-12-13 22:59:08 +00:00
getPathAsync: function(callback) {
2017-02-18 13:26:21 +00:00
if (!currentSession || !currentSession.getStatus)
return callback("not ready");
2016-12-13 22:59:08 +00:00
currentSession.getStatus({}, function(err, result) {
callback(err, result && util.normalizePath(result.path));
});
},
2016-06-26 11:53:19 +00:00
// toggleMouse : toggleMouse,
// toggleStatus : toggleStatus,
// closeActivePane : closeActivePane,
// splitPaneH : splitPaneH,
// splitPaneV : splitPaneV,
// moveUp : moveUp,
// moveDown : moveDown,
// moveLeft : moveLeft,
// moveRight : moveRight,
/**
* @internal
* @ignore
*/
Aceterm: Aceterm
});
plugin.load((isOutputTerminal ? "output" : "terminal") + counter++);
return plugin;
}
register(null, {
terminal: handle
});
}
});