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

600 wiersze
22 KiB
JavaScript

define(function(require, module, exports) {
main.consumes = [
"Plugin", "tabManager", "menus", "settings", "layout", "ui",
"commands", "anims"
];
main.provides = ["console"];
return main;
function main(options, imports, register) {
var Plugin = imports.Plugin;
var ui = imports.ui;
var tabs = imports.tabManager;
var settings = imports.settings;
var menus = imports.menus;
var anims = imports.anims;
var commands = imports.commands;
var layout = imports.layout;
var cssString = require("text!./style.css");
var markup = require("text!./console.xml");
/***** Initialization *****/
var plugin = new Plugin("Ajax.org", main.consumes);
var emit = plugin.getEmitter();
var onFinishTimer, consoleRow, animating;
var container, changed, maximized, maxHeight, lastZIndex, height;
var hidden = true;
var minHeight = 60;
var collapsedHeight = 0;
var loaded = false;
function load(){
if (loaded) return false;
loaded = true;
// Commands
commands.addCommand({
name: "toggleconsole",
group: "Panels",
bindKey: {mac: "Ctrl-Esc", win: "F6"},
exec: function(editor, args) {
var el;
if (hidden || args.show) {
show();
el = container;
} else {
hide();
el = tabs.container;
}
var pane = tabs.findPane(container.$activePaneName);
var tab = pane && pane.activeTab || tabs.getTabs(el)[0];
tabs.focusTab(tab);
}
}, plugin);
// Menus
menus.addItemByPath("View/Console", new apf.item({
type: "check",
command: "toggleconsole",
checked: "state/console/@expanded"
}), 700, plugin);
// Settings
settings.on("read", function(e) {
// Defaults
settings.setDefaults("state/console", [
["expanded", "true"],
["maximized", "false"],
["height", "153"]
]);
// Height
height = Math.max(minHeight, Math.min(layout.maxConsoleHeight,
settings.getNumber("state/console/@height") || 0));
setTimeout(function() {
// Expanded
if (settings.getBool("state/console/@expanded"))
show(true);
// Maximized
if (settings.getBool("state/console/@maximized"))
maximizeConsoleHeight();
}, 0);
changed = false;
}, plugin);
settings.on("write", function(e) {
// if (!changed)
// return;
if (!container) return;
if (drawn) {
var state = getState(true);
settings.setJson("state/console", state);
}
}, plugin);
tabs.on("focus", function(e) {
if (drawn && ui.isChildOf(container, e.tab.aml))
show();
});
if (apf.isGecko) {
ui.setStyleRule(".console .btnsesssioncontainer .inside",
"padding-right", "30px");
}
// @todo fix Focus Handling in pane
}
var drawn = false;
function draw(){
if (drawn) return;
drawn = true;
ui.insertCss(cssString, options.staticPrefix, plugin);
consoleRow = layout.findParent(plugin);
container = consoleRow.appendChild(new ui.bar({
zindex: 99,
height: height,
// minheight : 60,
"class" : "console codeditorHolder"
}));
plugin.addElement(container);
tabs.containers.push(container);
if (settings.getBool("user/tabs/@asterisk"))
ui.setStyleClass(container, "asterisk");
ui.insertMarkup(container, markup, plugin);
// Track splitter and update state
var splitter = consoleRow.$handle;
splitter && splitter.on("dragdrop", function(e) {
height = Math.max(minHeight, container.height);
if (height)
settings.set("state/console/@height", height);
emit("resize");
});
plugin.getElement("btnClose").onclick = function(){
hide();
this.blur();
};
plugin.getElement("btnConsoleMax").onclick = function(){
if (this.value)
maximizeConsoleHeight();
else
restoreConsoleHeight();
};
// Create new tabs
var state = settings.getJson("state/console");
if (!state) {
state = options.defaultState || {
type: "pane",
nodes: [
{
type: "tab",
editorType: "terminal",
active: "true"
},
{
type: "tab",
editorType: "immediate",
document: {
title: "Immediate"
}
}
]
};
}
if (options.testing != 2) {
setState(state, true, function(){});
emit.sticky("ready");
}
tabs.on("paneCreate", function(e) {
if (hidden && container && ui.isChildOf(container, e.pane.aml)) {
e.pane._visible = false;
}
});
emit("draw");
}
/***** Methods *****/
function getState(filter) {
var state = tabs.getState(container.lastChild, filter);
var tab = tabs.focus && tabs.findTab(state.focus);
if (!tab || !ui.isChildOf(container, tab.pane.aml))
delete state.focus;
return state;
}
function setState(state, init, callback) {
state.container = container;
return tabs.setState(state, init, callback);
}
function getTabs(){
return tabs.getTabs(container);
}
function getPanes(){
return tabs.getPanes(container);
}
function clear(){
var tabNodes = tabs.getPanes(container);
for (var i = tabNodes.length - 1; i >= 0; i--) {
var pane = tabNodes[i], nodes = pane.getTabs();
if (!ui.isChildOf(container, pane.aml))
continue;
for (var j = nodes.length - 1; j >= 0; j--) {
var tab = nodes[j];
tab.unload();
}
pane.unload();
}
}
function maximizeConsoleHeight(){
if (maximized)
return;
maximized = true;
apf.document.documentElement.appendChild(container);
var top = layout.getElement("root").getTop() + 1;
container.setAttribute('anchors', top + ' 0 0 0');
lastZIndex = container.$ext.style.zIndex;
container.removeAttribute('height');
container.$ext.style.maxHeight = "10000px";
container.$ext.style.zIndex = 100000;
settings.set("state/console/@maximized", true);
plugin.getElement("btnConsoleMax").setValue(true);
setTimeout(function(){
getPanes().forEach(function(pane) {
var tab = pane.activeTab;
if (tab)
tab.editor.resize();
});
});
emit("resize");
}
function restoreConsoleHeight(){
if (!maximized)
return;
maximized = false;
layout.findParent(plugin).appendChild(container);
container.removeAttribute('anchors');
maxHeight = window.innerHeight - 70;
// container.$ext.style.maxHeight = maxHeight + "px";
container.setAttribute('height', maxHeight && height > maxHeight ? maxHeight : height);
container.$ext.style.zIndex = lastZIndex;
settings.set("state/console/@maximized", false);
plugin.getElement("btnConsoleMax").setValue(false);
getPanes().forEach(function(pane) {
var tab = pane.activeTab;
if (tab)
tab.editor.resize();
});
emit("resize");
}
function hide(immediate) { show(immediate, true); }
function show(immediate, shouldHide) {
if (!shouldHide)
draw();
if (hidden == shouldHide || animating)
return;
hidden = shouldHide;
animating = true;
maxHeight = window.innerHeight - 70;
getPanes().forEach(function(pane) {
pane._visible = !shouldHide;
});
if (!shouldHide && !tabs.focussedTab) {
getPanes().some(function(pane) {
if (pane.getTab()) {
tabs.focusTab(pane.getTab());
return true;
}
});
}
var finish = function() {
if (onFinishTimer)
clearTimeout(onFinishTimer);
onFinishTimer = setTimeout(function(){
if (shouldHide) {
container.hide();
// If the focussed tab is in the console, make the first
// tab we can find inside the tabs the focussed tab.
if (tabs.focussedTab
&& getPanes().indexOf(tabs.focussedTab.pane) > -1) {
tabs.getPanes(tabs.container).every(function(pane) {
var tab = pane.getTab();
if (!tab) {
tabs.focusTab(); // blur
return true;
}
else {
tabs.focusTab(tab);
return false;
}
});
}
}
else {
container.$ext.style.minHeight = minHeight + "px";
container.minheight = minHeight;
maxHeight = window.innerHeight - 70;
// container.$ext.style.maxHeight = maxHeight + "px";
}
container.height = height + 1;
container.setAttribute("height", height);
container.$ext.style[apf.CSSPREFIX + "TransitionDuration"] = "";
animating = false;
settings.set("state/console/@expanded", !shouldHide);
apf.layout.forceResize();
emit("resize");
}, 100);
};
var toHeight;
var animOn = settings.getBool("user/general/@animateui");
if (!shouldHide) {
toHeight = Math.max(minHeight, Math.min(maxHeight, height));
container.$ext.style.minHeight = 0;
container.setHeight(collapsedHeight);
//container.$ext.style.height = collapsedHeight + "px";
container.show();
anims.animateSplitBoxNode(container, {
height: toHeight + "px",
immediate: immediate || !animOn,
duration: 0.2,
timingFunction: "cubic-bezier(.30, .08, 0, 1)"
}, finish);
}
else {
toHeight = collapsedHeight;
if (container.parentNode != consoleRow)
restoreConsoleHeight();
container.$ext.style.minHeight = 0;
container.$ext.style.maxHeight = "10000px";
anims.animateSplitBoxNode(container, {
height: toHeight + "px",
immediate: immediate || !animOn,
duration: 0.2,
timingFunction: "ease-in-out"
}, finish);
}
}
function openFile(path, active, callback) {
if (typeof active == "function")
callback = active, active = false;
open({path: path, active: active}, callback);
}
function openEditor(type, active, callback) {
if (typeof active == "function")
callback = active, active = false;
open({editorType: type, active: active}, callback);
}
function open(options, callback) {
if (!options.pane) {
draw();
options.pane = container.getElementsByTagNameNS(apf.ns.aml, "tab")[0].cloud9pane;
}
return tabs.open(options, callback);
}
/***** Lifecycle *****/
plugin.on("load", function(){
load();
});
plugin.on("enable", function(){
});
plugin.on("disable", function(){
});
plugin.on("unload", function(){
tabs.containers.remove(container);
clear();
loaded = false;
});
/***** Register and define API *****/
/**
* A collapsable panel in the bottom of Cloud9's UI that contains panes,
* tabs and editors.
*
* To open new tabs in the console you can use:
*
* console.open({
* path : "/file.txt",
* active : true
* }, function(){});
*
* This is equivalent to using the {@link tabManager} to open new tabs:
*
* tabManager.open({
* path : "/file.txt",
* active : true,
* pane : console.getPanes()[0]
* }, function(){});
*
* @singleton
*/
plugin.freezePublicAPI({
/**
* The AMLElement for the main panes area.
* @property {AMLElement} container
* @readonly
*/
get container(){ return container; },
_events: [
/**
* Fires after the console is drawn.
* @event draw
*/
"draw",
/**
* Fires when the console is resized
* @event resize
**/
"resize",
/**
* Fires when the console is ready for adding new tabs
*/
"ready"
],
/**
* Returns an array containing all the tabs in the console.
*/
getTabs: getTabs,
/**
* Returns an array containing all the panes in the console.
*/
getPanes: getPanes,
/**
* Retrieves the state of all panes, tabs and documents
* as a single serializable object.
* @returns {Object}
*/
getState: getState,
/**
* Loads the state of all panes panes, tabs anddocuments from a
* simple object.
* @param {Object} state The state describing the pane layout.
* @param {Boolean} [init] When set to true, optimizes the state
* loading for initialization of Cloud9.
* @param {Function} callback Called when the state loading is completed.
*/
setState: setState,
/**
* Removes all panes, except one, and destroys all tabs, documents
* and editors.
*
* @param {Boolean} [soft=false] When set to true clear
* will not unload tabs. This can be useful when loading a new
* state with exactly the same tabs. WARNING: this can lead to
* leaking tabs/documents, etc. Use with caution! getTabs() will
* continue to return all the tabs. Even though they are no longer
* attached to a pane.
*/
clear: clear,
/**
* Expands the console.
*/
show: show,
/**
* Collapses the console.
*/
hide: hide,
/**
* Opens a new tab in the console. If the tab with a specified
* path already exists, that tab is activated. If state is given
* for a document, then that state is set prior to
* loading the tab. If a path is specified the file contents is
* loaded into the document. If no editorType is specified, the
* editor is determined based on the extension of the filename.
*
* See also {@link tabManager#method-open}
*
* @param options
* @param {String} [options.path] The path of the file to open
* @param {Pane} [options.pane] The pane to attach the new tab to
* @param {String} [options.editorType] The type of the editor for this tab
* @param {Boolean} [options.active=false] Whether this tab is set as active
* @param {Boolean} [options.demandExisting=false] Whether to try opening an
* existing tab even for tabs without a path.
* @param {Object} [options.document] Object describing the
* state of the document (see {@link Document#method-getState})
* @param {String} [options.value] The contents of the file
* @param {String} [options.title] The title of the tab
* @param {String} [options.tooltip] The tooltip at the button of the tab
* @param {Function} callback
* @param {Error} callback.err An error that might
* occur during the load of the file contents.
* @param {Tab} callback.tab The created tab.
* @param {Function} callback.done Call this function
* when done retrieving the value. This is only relevant if
* -1 is passed to `value`. You are responsible for settings the
* document value yourself, like so: `tab.document.value = "value";`
* @returns {Tab} The created tab.
* @fires open
* @fires beforeOpen
*/
open: open,
/**
* Opens a new pane tab in the console with the default editor and loads the file
* contents into the document. This is a convenience method. For
* the full method description see {@link tabManager#method-open}.
*
* @param {String} path The path of the file to open.
* @param {Boolean} [active] When set to true the new tab will become active in it's pane.
* @param {Function} callback Called when the file contents is loaded.
* @param {Error} callback.err An error that might
* occur during the load of the file contents.
* @param {Tab} callback.tab The created tab.
* @returns {Tab} The created tab.
*/
openFile: openFile,
/**
* Opens a new tab in the console with a specific editor
* @param {String} editorType The type of the editor for this tab.
* @param {Boolean} [active] When set to true the new tab will become active in it's pane.
* @param {Function} callback Called when the editor is loaded.
* @param {Error} callback.err An error that might
* occur during the load of the editor.
* @param {Tab} callback.tab The created tab.
* @returns {Tab} The created tab.
*/
openEditor: openEditor
});
register(null, {
console: plugin
});
}
});