kopia lustrzana https://github.com/c9/core
573 wiersze
22 KiB
JavaScript
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
|
|
});
|
|
}
|
|
});
|