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

573 wiersze
22 KiB
JavaScript

define(function(require, exports, module) {
main.consumes = [
"editors", "ui", "settings", "tabManager", "ace", "menus", "commands",
"console", "ace.status"
];
main.provides = ["immediate"];
return main;
function main(options, imports, register) {
var editors = imports.editors;
var tabManager = imports.tabManager;
var ui = imports.ui;
var menus = imports.menus;
var commands = imports.commands;
var c9console = imports.console;
var aceHandle = imports.ace;
var aceStatus = imports["ace.status"];
var Repl = require("plugins/c9.ide.ace.repl/repl").Repl;
/***** Initialization *****/
var extensions = [];
var handle = editors.register("immediate", "Immediate Window",
Immediate, extensions);
var emit = handle.getEmitter();
var replTypes = {}; //Shared across Immediate windows
var theme, defaultEvaluator;
handle.on("load", function() {
handle.addElement(
tabManager.getElement("mnuEditors").appendChild(
new ui.item({
caption: "New Immediate Window",
onclick: function(e) {
tabManager.open({
active: true,
pane: this.parentNode.pane,
editorType: "immediate"
}, function() {});
}
})
)
);
menus.addItemByPath("Window/New Immediate Window", new ui.item({
onclick: function() {
tabManager.open({
active: true,
pane: this.parentNode.pane,
editorType: "immediate"
}, function() {});
}
}), 31, handle);
commands.addCommand({
name: "showimmediate",
group: "Panels",
exec: function (editor, args) {
// Search for the output pane
if (search(done)) return;
// If not found show the console
c9console.show();
// Search again
if (search(done)) return;
// Else open the output panel in the console
tabManager.open({
editorType: "immediate",
active: true,
pane: c9console.getPanes()[0],
}, done);
function done(err, tab) {
if (args && args.evaluator)
tab.editor.setActiveEvaluator(args.evaluator);
}
}
}, handle);
// Insert some CSS
ui.insertCss(require("text!./style.css"), null, handle);
});
// Search through pages
function search(cb) {
return !tabManager.getTabs().every(function(tab) {
if (tab.editorType == "immediate") {
tabManager.focusTab(tab);
if (cb) cb(null, tab);
return false;
}
return true;
});
}
function Immediate() {
var Baseclass = editors.findEditor("ace");
var plugin = new Baseclass(true, [], main.consumes);
// var emit = plugin.getEmitter();
var ddType, btnClear, ace, menu, statusbar;
plugin.on("draw", function(e) {
aceStatus.draw();
// Create UI elements
statusbar = e.tab.appendChild(new ui.bar({
skin: "bar-status",
skinset: "c9statusbar",
zindex: 10000,
height: 23,
style: "position:absolute;bottom:3px;right:5px;"
}));
menu = new ui.menu({
htmlNode: document.body,
render: "runtime",
class: "mnuSbPrefs"
});
plugin.addElement(menu);
ddType = ui.insertByIndex(statusbar, new ui.button({
skin: "label",
style: "cursor:pointer",
margin: "1 7 0 0",
submenudir: "up",
submenu: menu
}), 1000, plugin);
btnClear = ui.insertByIndex(statusbar, new ui.button({
margin: "0 3 0 0",
skin: "btn_console",
skinset: "c9statusbar",
class: "clear",
submenudir: "up"
}), 100000, plugin);
ace = plugin.ace;
ace.setOption("printMargin", false);
ace.setOption("scrollPastEnd", 0);
ace.setOption("showFoldWidgets", false);
ace.setOption("highlightActiveLine", false);
ace.setOption("highlightGutterLine", false);
ace.renderer.setScrollMargin(0, 10);
commands.addCommand({
bindKey: "Ctrl-C",
name: "abortimmediateexpression",
isAvailable: function() {
var tab = tabManager.focussedTab;
if (tab && tab.editorType == "immediate" && tab.editor && tab.editor.ace)
return tab.editor.ace.selection.isEmpty() && tab.editor.ace.isFocused();
},
exec: function() { abort(); }
}, plugin);
e.htmlNode.className += " immediate";
// Allow Selection (APF)
e.tab.textselect = true;
ddType.on("afterchange", function() {
if (currentDocument)
currentDocument.getSession().changeType(ddType.selectedType);
});
btnClear.on("click", function() {
plugin.clear();
btnClear.blur();
});
for (var type in replTypes) {
var t = replTypes[type];
addType(t.caption, type, t.plugin);
}
handle.on("addEvaluator", function(e) {
addType(e.caption, e.id, e.plugin);
});
menu.on("itemclick", function(e) {
var value = e.relatedNode.getAttribute("value");
ddType.setType(value);
});
function update(e) {
if (e && !e.value)
return;
var items = menu.childNodes;
var value = ddType.selectedType || items[0].getAttribute("value");
var selectedItem;
items.forEach(function(item) {
var selected = item.getAttribute("value") == value ? true : false;
if (selected) selectedItem = item;
item.setAttribute("selected", selected);
});
ddType.setType(value);
}
ddType.setType = function (type) {
if (type == ddType.selectedType)
return;
var caption = replTypes[type]
? replTypes[type].caption
: type + " (Unknown)";
ddType.selectedType = type;
ddType.setAttribute("caption", caption);
ddType.dispatchEvent("afterchange");
};
function setTheme(e) {
theme = e.theme;
if (!theme) return;
btnClear.parentNode.$ext.className = "bar-status "
+ (theme.isDark ? "ace_dark" : "");
}
aceHandle.on("themeChange", setTheme, plugin);
setTheme({ theme: aceHandle.theme });
menu.on("prop.visible", update);
update();
});
/***** Method *****/
function addType(caption, value, plugin) {
var item = menu.appendChild(new ui.item({
caption: caption,
value: value,
type: "radio"
}));
if (ddType.selectedType == value)
ddType.setAttribute("caption", caption);
plugin.addOther(function() {
if (item.parentNode)
item.parentNode.removeChild(item);
});
}
function setActiveEvaluator(value) {
ddType.setType(value);
}
function getActiveEvaluator() {
return ddType.selectedType;
}
function abort() {
var session = currentDocument.getSession();
if (session.repl.evaluator.abort)
session.repl.evaluator.abort();
}
// Set the tab in loading state - later this could be the output block
// currentDocument.tab.classList.add("loading");
// settings.save();
/***** Lifecycle *****/
plugin.on("load", function() {
});
var currentDocument;
plugin.on("documentLoad", function(e) {
var doc = e.doc;
var session = doc.getSession();
doc.undoManager.on("change", function(e) {
if (!doc.undoManager.isAtBookmark())
doc.undoManager.bookmark();
});
doc.tooltip =
doc.title = "Immediate";
if (session.repl) return;
session.changeType = function(type, noMessage) {
if (session.repl && session.repl.type == type)
return;
handle.findEvaluator(type, function(type, evaluator) {
session.type = type;
if (!session.repl) {
session.repl = new Repl(session.session, {
mode: evaluator.mode,
evaluator: evaluator,
message: evaluator.message
});
if (currentDocument
&& currentDocument.getSession() == session)
session.repl.attach(ace);
}
session.repl.type = type;
session.repl.clear();
if (!noMessage) {
var cell = session.repl.insertCell(
{ row: 0, column: 0 }, { type: "comment" }, true);
cell.setValue(evaluator.message + "\n");
}
if (session.repl.evaluator && session.repl.evaluator != evaluator)
session.repl.evaluator.cleanUp("elements");
session.repl.ensureLastInputCell();
session.repl.setEvaluator(evaluator);
session.repl.session.setMode(evaluator.mode);
evaluator.draw(statusbar);
doc.tooltip =
doc.title = "Immediate (" + evaluator.caption + ")";
});
};
doc.value = "";
session.changeType(session.type || defaultEvaluator || ddType.selectedType);
});
plugin.on("documentActivate", function(e) {
currentDocument = e.doc;
var session = e.doc.getSession();
if (session.type) {
ddType.setType(session.type);
}
if (session.repl)
session.repl.attach(ace);
});
plugin.on("documentUnload", function(e) {
var session = e.doc.getSession();
if (session.repl)
session.repl.detach();
// TODO: this breaks moving repl between splits
// delete session.repl;
});
plugin.on("getState", function(e) {
var session = e.doc.getSession();
if (!session.repl || e.filter) return;
e.state.type = session.type;
var data = session.repl.history._data;
var pos = session.repl.history.position;
if (data.length > 1000)
data = data.slice(-500);
e.state.history = data;
e.state.pos = pos;
});
plugin.on("setState", function(e) {
if (e.state.type) {
var session = e.doc.getSession();
session.type = e.state.type;
session.repl.history._data = e.state.history || [];
if (typeof e.state.pos === "number")
session.repl.history.position = e.state.pos;
if (e.doc == currentDocument)
ddType.setType(e.state.type);
}
});
plugin.on("clear", function() {
if (currentDocument) {
plugin.focus();
}
});
plugin.on("focus", function() {
});
plugin.on("enable", function() {
});
plugin.on("disable", function() {
});
plugin.on("unload", function() {
});
/***** Register and define API *****/
/**
* Immediate Pane for Cloud9
* @class immediate.Immediate
* @extends Editor
*/
/**
* The type of editor. Use this to create an immediate pane using
* {@link tabManager#openEditor} or {@link editors#createEditor}.
* @property {"immediate"} type
* @readonly
*/
plugin.freezePublicAPI({
/**
*
*/
get statusbar() { return statusbar; },
/**
*
*/
setActiveEvaluator: setActiveEvaluator,
/**
*
*/
getActiveEvaluator: getActiveEvaluator,
/**
* @ignore This is here to overwrite default behavior
*/
isClipboardAvailable: function(e) { return !e.fromKeyboard; }
});
plugin.load(null, "immediate");
return plugin;
}
/**
* The immediate handle, provides an API for adding
* {@link Evaluator evaluators} to the immediate panes.
* An evaluator is a plugin that can take the expressions from the
* multi-line REPL and return resuls. The results can be
* rendered as HTML and are fully interactive.
*
* This is the object you get when you request the immediate service
* in your plugin.
*
* Example:
*
* define(function(require, exports, module) {
* main.consumes = ["immediate", "Plugin"];
* main.provides = ["myplugin"];
* return main;
*
* function main(options, imports, register) {
* var immediate = imports.immediate;
* var plugin = new imports.Plugin("Your Name", main.consumes);
*
* plugin.on("load", function(){
* var evaluator = {
* mode : "ace/mode/go",
* message : "",
* canEvaluate : function(str) { return str.trim() ? true : false; },
* evaluate : function(expression, cell, done) {
*
* executeCommand(expression, function(result) {
* cell.addWidget({
* html : "<div class='result'>"
* + result + "</div>",
* coverLine : true,
* fixedWidth : true
* });
*
* done();
* });
*
* }
* };
*
* immediate.addEvaluator("Go Language", "go", evaluator, plugin);
* });
* });
* });
*
*
* @class immediate
* @extends Plugin
* @singleton
*/
handle.freezePublicAPI({
get defaultEvaluator() { return defaultEvaluator; },
set defaultEvaluator(value) { defaultEvaluator = value; },
_events: [
/**
* Fires when an evaluator is added.
* @event addEvaluator
* @param {Object} e
* @param {String} e.caption The caption of the evaluator.
* @param {String} e.id The unique identifier of the evaluator.
* @param {Evaluator} e.evaluator The evaluator.
* @param {Plugin} e.plugin The plugin responsible for adding the evaluator.
*/
"addEvaluator",
/**
* Fires when an evaluator is removed.
* @event removeEvaluator
* @param {Object} e
* @param {String} e.caption The caption of the evaluator.
* @param {String} e.id The unique identifier of the evaluator.
* @param {Evaluator} e.evaluator The evaluator.
* @param {Plugin} e.plugin The plugin responsible for adding the evaluator.
*/
"removeEvaluator"
],
/**
* Adds a new evaluator to all immediate panes. The user is able
* to choose the evaluator from a dropdown in the UI of the
* immediate pane.
* @param {String} caption The caption in the dropdown.
* @param {String} id The unique identifier of this evaluator.
* @param {Evaluator} evaluator The evaluator for your runtime.
* @param {Plugin} plugin The plugin responsible for adding the evaluator.
* @fires addEvaluator
*/
addEvaluator: function(caption, id, evaluator, plugin) {
if (replTypes[id])
throw new Error("An evaluator is already registered with "
+ "the id '" + id + "'");
replTypes[id] = {
caption: caption,
id: id,
evaluator: evaluator,
plugin: plugin
};
emit("addEvaluator", replTypes[id]);
plugin.addOther(function() {
handle.removeEvaluator(id);
});
},
/**
* Retrieves an evaluator based on it's id. When the evaluator is
* not yet registered, the callback will be returned when the
* evaluator is registered.
* @param {String} id The id of the evaluator to retrieve.
* @param {Function} callback Called when the evaluator is available.
* @param {Error} callback.id The id of the requested evaluator.
* @param {Evaluator} callback.evaluator The evaluator requested.
*/
findEvaluator: function(id, callback) {
if (!id || !replTypes[id]) {
handle.on("addEvaluator", function wait(e) {
if (!id || e.id == id)
callback(e.id, replTypes[e.id].evaluator);
handle.off("addEvaluator", wait);
});
}
else {
callback(id, replTypes[id].evaluator);
}
},
/**
* Removes an evaluator from all immediate panes.
* @param {String} id The unique identifier of the evaluator to remove.
*/
removeEvaluator: function(id) {
emit("removeEvaluator", replTypes[id]);
delete replTypes[id];
}
});
register(null, {
immediate: handle
});
}
});