kopia lustrzana https://github.com/c9/core
1340 wiersze
55 KiB
JavaScript
1340 wiersze
55 KiB
JavaScript
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 = {
|
||
"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]
|
||
};
|
||
|
||
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);
|
||
|
||
handle.on("load", function(){
|
||
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
|
||
}, function(){});
|
||
}
|
||
}, 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",
|
||
hotkey: "{commands.commandManager.openterminal}",
|
||
onclick: function(e) {
|
||
tabs.open({
|
||
active: true,
|
||
pane: this.parentNode.pane,
|
||
editorType: "terminal"
|
||
}, function(){});
|
||
}
|
||
}), 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);
|
||
|
||
function setSettings(){
|
||
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();
|
||
|
||
var skin = e.oldTheme;
|
||
if (!(settings.get("user/terminal/@backgroundColor") == defaults[skin][0] &&
|
||
settings.get("user/terminal/@foregroundColor") == defaults[skin][1] &&
|
||
settings.get("user/terminal/@selectionColor") == defaults[skin][2] &&
|
||
settings.get("user/terminal/@antialiasedfonts") == defaults[skin][3]))
|
||
return false;
|
||
});
|
||
|
||
layout.on("themeDefaults", function(e) {
|
||
var skin = e.theme;
|
||
settings.set("user/terminal/@backgroundColor", defaults[skin][0]);
|
||
settings.set("user/terminal/@foregroundColor", defaults[skin][1]);
|
||
settings.set("user/terminal/@selectionColor", defaults[skin][2]);
|
||
settings.set("user/terminal/@antialiasedfonts", defaults[skin][3]);
|
||
}, handle);
|
||
|
||
// Settings UI
|
||
|
||
prefs.add({
|
||
"Editors" : {
|
||
"Terminal" : {
|
||
position: 100,
|
||
"Text Color" : {
|
||
type: "colorbox",
|
||
path: "user/terminal/@foregroundColor",
|
||
position: 10100
|
||
},
|
||
"Background Color" : {
|
||
type: "colorbox",
|
||
path: "user/terminal/@backgroundColor",
|
||
position: 10200
|
||
},
|
||
"Selection Color" : {
|
||
type: "colorbox",
|
||
path: "user/terminal/@selectionColor",
|
||
position: 10250
|
||
},
|
||
"Font Family" : {
|
||
type: "textbox",
|
||
path: "user/terminal/@fontfamily",
|
||
position: 10300
|
||
},
|
||
"Font Size" : {
|
||
type: "spinner",
|
||
path: "user/terminal/@fontsize",
|
||
min: "1",
|
||
max: "72",
|
||
position: 11000
|
||
},
|
||
"Antialiased Fonts" : {
|
||
type: "checkbox",
|
||
path: "user/terminal/@antialiasedfonts",
|
||
position: 12000
|
||
},
|
||
"Blinking Cursor" : {
|
||
type: "checkbox",
|
||
path: "user/terminal/@blinking",
|
||
position: 12000
|
||
},
|
||
"Scrollback" : {
|
||
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");
|
||
}
|
||
});
|
||
});
|
||
handle.on("unload", function(){
|
||
mnuTerminal = null;
|
||
lastEditor = null;
|
||
lastTerminal = null;
|
||
shownDotsHelp = null;
|
||
installPrompted = null;
|
||
});
|
||
|
||
handle.draw = function(){
|
||
ui.insertMarkup(null, markupMenu, handle);
|
||
mnuTerminal = handle.getElement("mnuTerminal");
|
||
|
||
if (c9.platform == "win32") {
|
||
var nodes = mnuTerminal.childNodes;
|
||
while (nodes[6]) {
|
||
mnuTerminal.removeChild(nodes[6]);
|
||
}
|
||
}
|
||
|
||
handle.draw = function(){};
|
||
};
|
||
|
||
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");
|
||
});
|
||
|
||
handle.on("settingsUpdate", function(){
|
||
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);
|
||
};
|
||
|
||
plugin.on("unload", function(){
|
||
aceterm.destroy();
|
||
container.innerHTML = "";
|
||
|
||
aceterm = null;
|
||
container = null;
|
||
});
|
||
|
||
aceterm.on("focus", function(){
|
||
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);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
function focus(){
|
||
if (aceterm)
|
||
aceterm.focus();
|
||
}
|
||
|
||
function blur(){
|
||
// 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;
|
||
aceSession.c9session.getStatus({clients:true}, function(e, s) {
|
||
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 can’t 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’)",
|
||
function(){ // Hide
|
||
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;
|
||
|
||
html.push("<div style='height:", coverHeight, "px;", "left:0; right: ", screenRight, "px; top:", screenBottom, "px;' ",
|
||
"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);
|
||
}
|
||
function isTmuxBorderChar(x){ return !x || x[1] && "\xb7\u2500\u2502\u2518".indexOf(x[1]) != -1 }
|
||
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);
|
||
session.getStatus({clients: true}, function(e, status) {
|
||
if (e) 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
|
||
aceterm.renderer.once("afterRender", function start(){
|
||
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();
|
||
|
||
session.__defineGetter__("tab", function(){ return doc.tab });
|
||
session.__defineGetter__("doc", function(){ return doc });
|
||
session.__defineGetter__("defaultEditor", function(){
|
||
return settings.getBool("user/terminal/@defaultEnvEditor");
|
||
});
|
||
|
||
session.attach = function(){
|
||
if (session.aceSession && aceterm) {
|
||
aceterm.setSession(session.aceSession);
|
||
aceterm.container.style.display = "block";
|
||
}
|
||
else
|
||
aceterm.container.style.display = "none";
|
||
};
|
||
|
||
session.detach = function(){
|
||
// if (session.aceSession)
|
||
// aceterm.setSession(session.aceSession);
|
||
};
|
||
|
||
session.kill = function(){
|
||
tmuxConnection.kill(this);
|
||
};
|
||
|
||
session.warn = function(err){
|
||
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?",
|
||
function(){ // Yes
|
||
installer.reinstall("Cloud9 IDE");
|
||
},
|
||
function(){ // No
|
||
// Do nothing
|
||
});
|
||
}
|
||
}
|
||
|
||
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();
|
||
};
|
||
|
||
function setTabColor(){
|
||
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
|
||
doc.on("unload", function(){
|
||
// 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) {
|
||
session.connect = function(){
|
||
session.connect = function(){};
|
||
|
||
// Connect to a new or attach to an existing tmux session
|
||
createTerminal(session, e.state);
|
||
|
||
// Resize
|
||
session.resize();
|
||
};
|
||
|
||
return;
|
||
}
|
||
|
||
var tab = doc.tab;
|
||
tab.on("beforeClose", function(){
|
||
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.",
|
||
function(){ // Yes
|
||
tab.meta.$ignore = true;
|
||
tab.close();
|
||
|
||
if (question.dontAsk)
|
||
settings.set("user/terminal/noclosequestion", "true");
|
||
},
|
||
function(){ // No
|
||
// do nothing; allow user to continue
|
||
|
||
if (question.dontAsk)
|
||
settings.set("user/terminal/noclosequestion", "true");
|
||
},
|
||
{ showDontAsk: true });
|
||
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);
|
||
}
|
||
});
|
||
|
||
plugin.on("clear", function(){
|
||
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;
|
||
});
|
||
|
||
plugin.on("blur", function(){
|
||
blur();
|
||
});
|
||
|
||
plugin.on("resize", function(e) {
|
||
resize(e);
|
||
});
|
||
|
||
plugin.on("enable", function(){
|
||
|
||
});
|
||
|
||
plugin.on("disable", function(){
|
||
|
||
});
|
||
|
||
plugin.on("unload", function(){
|
||
|
||
});
|
||
|
||
/***** 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
|
||
*/
|
||
get ace(){ return aceterm; },
|
||
|
||
/**
|
||
* The HTMLElement containing the termainl.
|
||
* @property {HTMLElement} container
|
||
* @readonly
|
||
*/
|
||
get container(){ return container; },
|
||
|
||
_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
|
||
*/
|
||
isClipboardAvailable: function(e) { return !e.fromKeyboard },
|
||
|
||
/**
|
||
* 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,
|
||
|
||
// 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
|
||
});
|
||
}
|
||
});
|