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 : "
" * + result + "
", * 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 }); } });