diff --git a/IPython/html/static/base/js/utils.js b/IPython/html/static/base/js/utils.js index 7f9faf158..ad038722e 100644 --- a/IPython/html/static/base/js/utils.js +++ b/IPython/html/static/base/js/utils.js @@ -390,6 +390,18 @@ IPython.utils = (function (IPython) { test.remove(); return Math.floor(points*pixel_per_point); }; + + var always_new = function (constructor) { + // wrapper around contructor to avoid requiring `var a = new constructor()` + // useful for passing constructors as callbacks, + // not for programmer laziness. + // from http://programmers.stackexchange.com/questions/118798 + return function () { + var obj = Object.create(constructor.prototype); + constructor.apply(obj, arguments); + return obj; + }; + }; var url_path_join = function () { @@ -447,6 +459,7 @@ IPython.utils = (function (IPython) { points_to_pixels : points_to_pixels, url_path_join : url_path_join, splitext : splitext, + always_new : always_new, browser : browser }; diff --git a/IPython/html/static/notebook/js/codecell.js b/IPython/html/static/notebook/js/codecell.js index b823fa2de..23e70c00e 100644 --- a/IPython/html/static/notebook/js/codecell.js +++ b/IPython/html/static/notebook/js/codecell.js @@ -17,7 +17,7 @@ /* local util for codemirror */ -var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;} +var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}; /** * @@ -27,16 +27,16 @@ var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;} */ CodeMirror.commands.delSpaceToPrevTabStop = function(cm){ var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to); - if (!posEq(from, to)) {cm.replaceRange("", from, to); return} + if (!posEq(from, to)) { cm.replaceRange("", from, to); return; } var cur = cm.getCursor(), line = cm.getLine(cur.line); var tabsize = cm.getOption('tabSize'); var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize; - var from = {ch:cur.ch-chToPrevTabStop,line:cur.line} - var select = cm.getRange(from,cur) - if( select.match(/^\ +$/) != null){ - cm.replaceRange("",from,cur) + from = {ch:cur.ch-chToPrevTabStop,line:cur.line}; + var select = cm.getRange(from,cur); + if( select.match(/^\ +$/) !== null){ + cm.replaceRange("",from,cur); } else { - cm.deleteH(-1,"char") + cm.deleteH(-1,"char"); } }; @@ -104,7 +104,7 @@ var IPython = (function (IPython) { * @method auto_highlight */ CodeCell.prototype.auto_highlight = function () { - this._auto_highlight(IPython.config.cell_magic_highlight) + this._auto_highlight(IPython.config.cell_magic_highlight); }; /** @method create_element */ @@ -117,7 +117,7 @@ var IPython = (function (IPython) { this.celltoolbar = new IPython.CellToolbar(this); var input = $('
').addClass('input'); - var vbox = $('
').addClass('vbox box-flex1') + var vbox = $('
').addClass('vbox box-flex1'); input.append($('
').addClass('prompt input_prompt')); vbox.append(this.celltoolbar.element); var input_area = $('
').addClass('input_area'); @@ -152,7 +152,7 @@ var IPython = (function (IPython) { // they are sent, and remove tooltip if any, except for tab again if (event.type === 'keydown' && event.which != key.TAB ) { IPython.tooltip.remove_and_cancel_tooltip(); - }; + } var cur = editor.getCursor(); if (event.keyCode === key.ENTER){ @@ -177,7 +177,7 @@ var IPython = (function (IPython) { return false; } else { return true; - }; + } } else if (event.which === key.ESC) { return IPython.tooltip.remove_and_cancel_tooltip(true); } else if (event.which === key.DOWNARROW && event.type === 'keydown') { @@ -188,7 +188,7 @@ var IPython = (function (IPython) { return false; } else { return true; - }; + } } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) { if (editor.somethingSelected()){ var anchor = editor.getCursor("anchor"); @@ -203,7 +203,7 @@ var IPython = (function (IPython) { } else if (event.keyCode === key.TAB && event.type == 'keydown') { // Tab completion. //Do not trim here because of tooltip - if (editor.somethingSelected()){return false} + if (editor.somethingSelected()) { return false; } var pre_cursor = editor.getRange({line:cur.line,ch:0},cur); if (pre_cursor.trim() === "") { // Don't autocomplete if the part of the line before the cursor @@ -219,12 +219,12 @@ var IPython = (function (IPython) { event.stop(); this.completer.startCompletion(); return true; - }; + } } else { // keypress/keyup also trigger on TAB press, and we don't want to // use those to disable tab completion. return false; - }; + } return false; }; @@ -233,7 +233,7 @@ var IPython = (function (IPython) { CodeCell.prototype.set_kernel = function (kernel) { this.kernel = kernel; - } + }; /** * Execute current code cell to the kernel @@ -243,42 +243,65 @@ var IPython = (function (IPython) { this.output_area.clear_output(); this.set_input_prompt('*'); this.element.addClass("running"); - var callbacks = { - 'execute_reply': $.proxy(this._handle_execute_reply, this), - 'output': $.proxy(this.output_area.handle_output, this.output_area), - 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area), - 'set_next_input': $.proxy(this._handle_set_next_input, this), - 'input_request': $.proxy(this._handle_input_request, this) - }; + if (this.last_msg_id) { + this.kernel.clear_callbacks_for_msg(this.last_msg_id); + } + var callbacks = this.get_callbacks(); + this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true}); }; + + /** + * Construct the default callbacks for + * @method get_callbacks + */ + CodeCell.prototype.get_callbacks = function () { + return { + shell : { + reply : $.proxy(this._handle_execute_reply, this), + payload : { + set_next_input : $.proxy(this._handle_set_next_input, this), + page : $.proxy(this._open_with_pager, this) + } + }, + iopub : { + output : $.proxy(this.output_area.handle_output, this.output_area), + clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area), + }, + input : $.proxy(this._handle_input_request, this) + }; + }; + + CodeCell.prototype._open_with_pager = function (payload) { + $([IPython.events]).trigger('open_with_text.Pager', payload); + }; /** * @method _handle_execute_reply * @private */ - CodeCell.prototype._handle_execute_reply = function (content) { - this.set_input_prompt(content.execution_count); + CodeCell.prototype._handle_execute_reply = function (msg) { + this.set_input_prompt(msg.content.execution_count); this.element.removeClass("running"); $([IPython.events]).trigger('set_dirty.Notebook', {value: true}); - } + }; /** * @method _handle_set_next_input * @private */ - CodeCell.prototype._handle_set_next_input = function (text) { - var data = {'cell': this, 'text': text} + CodeCell.prototype._handle_set_next_input = function (payload) { + var data = {'cell': this, 'text': payload.text}; $([IPython.events]).trigger('set_next_input.Notebook', data); - } + }; /** * @method _handle_input_request * @private */ - CodeCell.prototype._handle_input_request = function (content) { - this.output_area.append_raw_input(content); - } + CodeCell.prototype._handle_input_request = function (msg) { + this.output_area.append_raw_input(msg); + }; // Basic cell manipulation. @@ -328,21 +351,23 @@ var IPython = (function (IPython) { CodeCell.input_prompt_classical = function (prompt_value, lines_number) { var ns = prompt_value || " "; - return 'In [' + ns + ']:' + return 'In [' + ns + ']:'; }; CodeCell.input_prompt_continuation = function (prompt_value, lines_number) { var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)]; - for(var i=1; i < lines_number; i++){html.push(['...:'])}; - return html.join('
') + for(var i=1; i < lines_number; i++) { + html.push(['...:']); + } + return html.join('
'); }; CodeCell.input_prompt_function = CodeCell.input_prompt_classical; CodeCell.prototype.set_input_prompt = function (number) { - var nline = 1 - if( this.code_mirror != undefined) { + var nline = 1; + if (this.code_mirror !== undefined) { nline = this.code_mirror.lineCount(); } this.input_prompt_number = number; @@ -407,16 +432,16 @@ var IPython = (function (IPython) { this.set_input_prompt(data.prompt_number); } else { this.set_input_prompt(); - }; + } this.output_area.fromJSON(data.outputs); if (data.collapsed !== undefined) { if (data.collapsed) { this.collapse(); } else { this.expand(); - }; - }; - }; + } + } + } }; @@ -426,7 +451,7 @@ var IPython = (function (IPython) { data.cell_type = 'code'; if (this.input_prompt_number) { data.prompt_number = this.input_prompt_number; - }; + } var outputs = this.output_area.toJSON(); data.outputs = outputs; data.language = 'python'; @@ -438,4 +463,4 @@ var IPython = (function (IPython) { IPython.CodeCell = CodeCell; return IPython; -}(IPython)); \ No newline at end of file +}(IPython)); diff --git a/IPython/html/static/notebook/js/completer.js b/IPython/html/static/notebook/js/completer.js index 992c87f24..3d9631c15 100644 --- a/IPython/html/static/notebook/js/completer.js +++ b/IPython/html/static/notebook/js/completer.js @@ -150,16 +150,14 @@ var IPython = (function (IPython) { matched_text: "" }) } else { - var callbacks = { - 'complete_reply': $.proxy(this.finish_completing, this) - }; - this.cell.kernel.complete(line, cur.ch, callbacks); + this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this)); } }; - Completer.prototype.finish_completing = function (content) { + Completer.prototype.finish_completing = function (msg) { // let's build a function that wrap all that stuff into what is needed // for the new completer: + var content = msg.content; var matched_text = content.matched_text; var matches = content.matches; diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js index d273bd181..e0ac198f6 100644 --- a/IPython/html/static/notebook/js/main.js +++ b/IPython/html/static/notebook/js/main.js @@ -96,7 +96,7 @@ function (marked) { // only do this once $([IPython.events]).off('notebook_loaded.Notebook', first_load); }; - + $([IPython.events]).on('notebook_loaded.Notebook', first_load); $([IPython.events]).trigger('app_initialized.NotebookApp'); IPython.notebook.load_notebook(notebookName, notebookPath); diff --git a/IPython/html/static/notebook/js/outputarea.js b/IPython/html/static/notebook/js/outputarea.js index 330700449..91886df25 100644 --- a/IPython/html/static/notebook/js/outputarea.js +++ b/IPython/html/static/notebook/js/outputarea.js @@ -231,9 +231,10 @@ var IPython = (function (IPython) { }; - OutputArea.prototype.handle_output = function (msg_type, content) { + OutputArea.prototype.handle_output = function (msg) { var json = {}; - json.output_type = msg_type; + var msg_type = json.output_type = msg.header.msg_type; + var content = msg.content; if (msg_type === "stream") { json.text = content.data; json.stream = content.name; @@ -564,9 +565,10 @@ var IPython = (function (IPython) { element.append(toinsert); }; - OutputArea.prototype.append_raw_input = function (content) { + OutputArea.prototype.append_raw_input = function (msg) { var that = this; this.expand(); + var content = msg.content; var area = this.create_output_area(); // disable any other raw_inputs, if they are left around @@ -618,8 +620,8 @@ var IPython = (function (IPython) { } - OutputArea.prototype.handle_clear_output = function (content) { - this.clear_output(content.wait); + OutputArea.prototype.handle_clear_output = function (msg) { + this.clear_output(msg.content.wait); }; diff --git a/IPython/html/static/notebook/js/tooltip.js b/IPython/html/static/notebook/js/tooltip.js index 9c3679e78..4cfadf666 100644 --- a/IPython/html/static/notebook/js/tooltip.js +++ b/IPython/html/static/notebook/js/tooltip.js @@ -218,16 +218,15 @@ var IPython = (function (IPython) { // remove everything after last open bracket line = line.replace(endBracket, ""); return Tooltip.last_token_re.exec(line) - } - + }; Tooltip.prototype._request_tooltip = function (cell, line) { - var callbacks = { - 'object_info_reply': $.proxy(this._show, this) - } + var callbacks = { shell : { + reply : $.proxy(this._show, this) + }}; var oir_token = this.extract_oir_token(line); var msg_id = cell.kernel.object_info_request(oir_token, callbacks); - } + }; // make an imediate completion request Tooltip.prototype.request = function (cell, hide_if_no_docstring) { @@ -301,7 +300,8 @@ var IPython = (function (IPython) { Tooltip.prototype._show = function (reply) { // move the bubble if it is not hidden // otherwise fade it - this.name = reply.name; + var content = reply.content; + this.name = content.name; // do some math to have the tooltip arrow on more or less on left or right // width of the editor @@ -334,20 +334,20 @@ var IPython = (function (IPython) { }); // build docstring - var defstring = reply.call_def; + var defstring = content.call_def; if (defstring == null) { - defstring = reply.init_definition; + defstring = content.init_definition; } if (defstring == null) { - defstring = reply.definition; + defstring = content.definition; } - var docstring = reply.call_docstring; + var docstring = content.call_docstring; if (docstring == null) { - docstring = reply.init_docstring; + docstring = content.init_docstring; } if (docstring == null) { - docstring = reply.docstring; + docstring = content.docstring; } if (docstring == null) { diff --git a/IPython/html/static/services/kernels/js/comm.js b/IPython/html/static/services/kernels/js/comm.js new file mode 100644 index 000000000..d9b32881a --- /dev/null +++ b/IPython/html/static/services/kernels/js/comm.js @@ -0,0 +1,198 @@ +//---------------------------------------------------------------------------- +// Copyright (C) 2013 The IPython Development Team +// +// Distributed under the terms of the BSD License. The full license is in +// the file COPYING, distributed as part of this software. +//---------------------------------------------------------------------------- + +//============================================================================ +// Comm and CommManager bases +//============================================================================ +/** + * Base Comm classes + * @module IPython + * @namespace IPython + * @submodule comm + */ + +var IPython = (function (IPython) { + "use strict"; + + //----------------------------------------------------------------------- + // CommManager class + //----------------------------------------------------------------------- + + var CommManager = function (kernel) { + this.comms = {}; + this.targets = {}; + if (kernel !== undefined) { + this.init_kernel(kernel); + } + }; + + CommManager.prototype.init_kernel = function (kernel) { + // connect the kernel, and register message handlers + this.kernel = kernel; + var msg_types = ['comm_open', 'comm_msg', 'comm_close']; + for (var i = 0; i < msg_types.length; i++) { + var msg_type = msg_types[i]; + kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this)); + } + }; + + CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) { + // Create a new Comm, register it, and open its Kernel-side counterpart + // Mimics the auto-registration in `Comm.__init__` in the IPython Comm + var comm = new Comm(target_name); + this.register_comm(comm); + comm.open(data, callbacks, metadata); + return comm; + }; + + CommManager.prototype.register_target = function (target_name, f) { + // Register a target function for a given target name + this.targets[target_name] = f; + }; + + CommManager.prototype.unregister_target = function (target_name, f) { + // Unregister a target function for a given target name + delete this.targets[target_name]; + }; + + CommManager.prototype.register_comm = function (comm) { + // Register a comm in the mapping + this.comms[comm.comm_id] = comm; + comm.kernel = this.kernel; + return comm.comm_id; + }; + + CommManager.prototype.unregister_comm = function (comm_id) { + // Remove a comm from the mapping + delete this.comms[comm_id]; + }; + + // comm message handlers + + CommManager.prototype.comm_open = function (msg) { + var content = msg.content; + var f = this.targets[content.target_name]; + if (f === undefined) { + console.log("No such target registered: ", content.target_name); + console.log("Available targets are: ", this.targets); + return; + } + var comm = new Comm(content.target_name, content.comm_id); + this.register_comm(comm); + try { + f(comm, msg); + } catch (e) { + console.log("Exception opening new comm:", e, msg); + comm.close(); + this.unregister_comm(comm); + } + }; + + CommManager.prototype.comm_close = function (msg) { + var content = msg.content; + var comm = this.comms[content.comm_id]; + if (comm === undefined) { + return; + } + delete this.comms[content.comm_id]; + try { + comm.handle_close(msg); + } catch (e) { + console.log("Exception closing comm: ", e, msg); + } + }; + + CommManager.prototype.comm_msg = function (msg) { + var content = msg.content; + var comm = this.comms[content.comm_id]; + if (comm === undefined) { + return; + } + try { + comm.handle_msg(msg); + } catch (e) { + console.log("Exception handling comm msg: ", e, msg); + } + }; + + //----------------------------------------------------------------------- + // Comm base class + //----------------------------------------------------------------------- + + var Comm = function (target_name, comm_id) { + this.target_name = target_name; + this.comm_id = comm_id || IPython.utils.uuid(); + this._msg_callback = this._close_callback = null; + }; + + // methods for sending messages + Comm.prototype.open = function (data, callbacks, metadata) { + var content = { + comm_id : this.comm_id, + target_name : this.target_name, + data : data || {}, + }; + return this.kernel.send_shell_message("comm_open", content, callbacks, metadata); + }; + + Comm.prototype.send = function (data, callbacks, metadata) { + var content = { + comm_id : this.comm_id, + data : data || {}, + }; + return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata); + }; + + Comm.prototype.close = function (data, callbacks, metadata) { + var content = { + comm_id : this.comm_id, + data : data || {}, + }; + return this.kernel.send_shell_message("comm_close", content, callbacks, metadata); + }; + + // methods for registering callbacks for incoming messages + Comm.prototype._register_callback = function (key, callback) { + this['_' + key + '_callback'] = callback; + }; + + Comm.prototype.on_msg = function (callback) { + this._register_callback('msg', callback); + }; + + Comm.prototype.on_close = function (callback) { + this._register_callback('close', callback); + }; + + // methods for handling incoming messages + + Comm.prototype._maybe_callback = function (key, msg) { + var callback = this['_' + key + '_callback']; + if (callback) { + try { + callback(msg); + } catch (e) { + console.log("Exception in Comm callback", e, msg); + } + } + }; + + Comm.prototype.handle_msg = function (msg) { + this._maybe_callback('msg', msg); + }; + + Comm.prototype.handle_close = function (msg) { + this._maybe_callback('close', msg); + }; + + IPython.CommManager = CommManager; + IPython.Comm = Comm; + + return IPython; + +}(IPython)); + diff --git a/IPython/html/static/services/kernels/js/kernel.js b/IPython/html/static/services/kernels/js/kernel.js index 48afd2fbb..9aa91aa2c 100644 --- a/IPython/html/static/services/kernels/js/kernel.js +++ b/IPython/html/static/services/kernels/js/kernel.js @@ -16,7 +16,8 @@ */ var IPython = (function (IPython) { - + "use strict"; + var utils = IPython.utils; // Initialization and connection. @@ -41,12 +42,15 @@ var IPython = (function (IPython) { this.WebSocket = MozWebSocket; } else { alert('Your browser does not have WebSocket support, please try Chrome, Safari or Firefox ≥ 6. Firefox 4 and 5 are also supported by you have to enable WebSockets in about:config.'); - }; + } + this.bind_events(); + this.init_iopub_handlers(); + this.comm_manager = new IPython.CommManager(this); }; - Kernel.prototype._get_msg = function (msg_type, content) { + Kernel.prototype._get_msg = function (msg_type, content, metadata) { var msg = { header : { msg_id : utils.uuid(), @@ -54,19 +58,32 @@ var IPython = (function (IPython) { session : this.session_id, msg_type : msg_type }, - metadata : {}, + metadata : metadata || {}, content : content, parent_header : {} }; return msg; }; - Kernel.prototype.bind_events = function() { + Kernel.prototype.bind_events = function () { var that = this; $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) { that.send_input_reply(data); }); - } + }; + + // Initialize the iopub handlers + + Kernel.prototype.init_iopub_handlers = function () { + var output_types = ['stream', 'display_data', 'pyout', 'pyerr']; + this._iopub_handlers = {}; + this.register_iopub_handler('status', $.proxy(this._handle_status_message, this)); + this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this)); + + for (var i=0; i < output_types.length; i++) { + this.register_iopub_handler(output_types[i], $.proxy(this._handle_output_message, this)); + } + }; /** * Start the Python kernel @@ -81,7 +98,7 @@ var IPython = (function (IPython) { $.proxy(this._kernel_started, this), 'json' ); - }; + } }; /** @@ -101,7 +118,7 @@ var IPython = (function (IPython) { $.proxy(this._kernel_started, this), 'json' ); - }; + } }; @@ -110,11 +127,11 @@ var IPython = (function (IPython) { this.running = true; this.kernel_id = json.id; var ws_url = json.ws_url; - if (ws_url.match(/wss?:\/\//) == null) { + if (ws_url.match(/wss?:\/\//) === null) { // trailing 's' in https will become wss for secure web sockets - prot = location.protocol.replace('http', 'ws') + "//"; + var prot = location.protocol.replace('http', 'ws') + "//"; ws_url = prot + location.host + ws_url; - }; + } this.ws_url = ws_url; this.kernel_url = utils.url_path_join(this.base_url, this.kernel_id); this.start_channels(); @@ -176,7 +193,7 @@ var IPython = (function (IPython) { } }, 1000); this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this); - this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this); + this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this); this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this); }; @@ -208,64 +225,62 @@ var IPython = (function (IPython) { var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; for (var i=0; i < channels.length; i++) { if ( channels[i] !== null ) { - channels[i].onclose = function (evt) {}; + channels[i].onclose = null; channels[i].close(); } - }; + } this.shell_channel = this.iopub_channel = this.stdin_channel = null; }; // Main public methods. + + // send a message on the Kernel's shell channel + Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) { + var msg = this._get_msg(msg_type, content, metadata); + this.shell_channel.send(JSON.stringify(msg)); + this.set_callbacks_for_msg(msg.header.msg_id, callbacks); + return msg.header.msg_id; + }; /** - * Get info on object asynchronoulsy + * Get info on an object * - * @async * @param objname {string} - * @param callback {dict} - * @method object_info_request + * @param callback {function} + * @method object_info * - * @example - * - * When calling this method pass a callbacks structure of the form: - * - * callbacks = { - * 'object_info_reply': object_info_reply_callback - * } - * - * The `object_info_reply_callback` will be passed the content object of the - * - * `object_into_reply` message documented in - * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) + * When calling this method, pass a callback function that expects one argument. + * The callback will be passed the complete `object_info_reply` message documented + * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information) */ - Kernel.prototype.object_info_request = function (objname, callbacks) { - if(typeof(objname)!=null && objname!=null) - { + Kernel.prototype.object_info = function (objname, callback) { + var callbacks; + if (callback) { + callbacks = { shell : { reply : callback } }; + } + + if (typeof(objname) !== null && objname !== null) { var content = { oname : objname.toString(), detail_level : 0, }; - var msg = this._get_msg("object_info_request", content); - this.shell_channel.send(JSON.stringify(msg)); - this.set_callbacks_for_msg(msg.header.msg_id, callbacks); - return msg.header.msg_id; + return this.send_shell_message("object_info_request", content, callbacks); } return; - } + }; /** * Execute given code into kernel, and pass result to callback. * - * TODO: document input_request in callbacks - * * @async * @method execute * @param {string} code - * @param [callbacks] {Object} With the optional following keys - * @param callbacks.'execute_reply' {function} - * @param callbacks.'output' {function} - * @param callbacks.'clear_output' {function} - * @param callbacks.'set_next_input' {function} + * @param [callbacks] {Object} With the following keys (all optional) + * @param callbacks.shell.reply {function} + * @param callbacks.shell.payload.[payload_name] {function} + * @param callbacks.iopub.output {function} + * @param callbacks.iopub.clear_output {function} + * @param callbacks.input {function} * @param {object} [options] * @param [options.silent=false] {Boolean} * @param [options.user_expressions=empty_dict] {Dict} @@ -287,27 +302,21 @@ var IPython = (function (IPython) { * When calling this method pass a callbacks structure of the form: * * callbacks = { - * 'execute_reply': execute_reply_callback, - * 'output': output_callback, - * 'clear_output': clear_output_callback, - * 'set_next_input': set_next_input_callback + * shell : { + * reply : execute_reply_callback, + * payload : { + * set_next_input : set_next_input_callback, + * } + * }, + * iopub : { + * output : output_callback, + * clear_output : clear_output_callback, + * }, + * input : raw_input_callback * } * - * The `execute_reply_callback` will be passed the content and metadata - * objects of the `execute_reply` message documented - * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute) - * - * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr') - * of the output and the content and metadata objects of the PUB/SUB channel that contains the - * output: - * - * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket - * - * The `clear_output_callback` will be passed a content object that contains - * stdout, stderr and other fields that are booleans, as well as the metadata object. - * - * The `set_next_input_callback` will be passed the text that should become the next - * input cell. + * Each callback will be passed the entire message as a single arugment. + * Payload handlers will be passed the corresponding payload and the execute_reply message. */ Kernel.prototype.execute = function (code, callbacks, options) { @@ -320,47 +329,39 @@ var IPython = (function (IPython) { allow_stdin : false }; callbacks = callbacks || {}; - if (callbacks.input_request !== undefined) { + if (callbacks.input !== undefined) { content.allow_stdin = true; } - $.extend(true, content, options) + $.extend(true, content, options); $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content}); - var msg = this._get_msg("execute_request", content); - this.shell_channel.send(JSON.stringify(msg)); - this.set_callbacks_for_msg(msg.header.msg_id, callbacks); - return msg.header.msg_id; + return this.send_shell_message("execute_request", content, callbacks); }; /** - * When calling this method pass a callbacks structure of the form: + * When calling this method, pass a function to be called with the `complete_reply` message + * as its only argument when it arrives. * - * callbacks = { - * 'complete_reply': complete_reply_callback - * } - * - * The `complete_reply_callback` will be passed the content object of the - * `complete_reply` message documented + * `complete_reply` is documented * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete) * * @method complete * @param line {integer} * @param cursor_pos {integer} - * @param {dict} callbacks - * @param callbacks.complete_reply {function} `complete_reply_callback` + * @param callback {function} * */ - Kernel.prototype.complete = function (line, cursor_pos, callbacks) { - callbacks = callbacks || {}; + Kernel.prototype.complete = function (line, cursor_pos, callback) { + var callbacks; + if (callback) { + callbacks = { shell : { reply : callback } }; + } var content = { text : '', line : line, block : null, cursor_pos : cursor_pos }; - var msg = this._get_msg("complete_request", content); - this.shell_channel.send(JSON.stringify(msg)); - this.set_callbacks_for_msg(msg.header.msg_id, callbacks); - return msg.header.msg_id; + return this.send_shell_message("complete_request", content, callbacks); }; @@ -368,7 +369,7 @@ var IPython = (function (IPython) { if (this.running) { $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this}); $.post(this.kernel_url + "/interrupt"); - }; + } }; @@ -380,7 +381,7 @@ var IPython = (function (IPython) { type : "DELETE" }; $.ajax(this.kernel_url, settings); - }; + } }; Kernel.prototype.send_input_reply = function (input) { @@ -396,9 +397,19 @@ var IPython = (function (IPython) { // Reply handlers + Kernel.prototype.register_iopub_handler = function (msg_type, callback) { + this._iopub_handlers[msg_type] = callback; + }; + + Kernel.prototype.get_iopub_handler = function (msg_type) { + // get iopub handler for a specific message type + return this._iopub_handlers[msg_type]; + }; + + Kernel.prototype.get_callbacks_for_msg = function (msg_id) { - var callbacks = this._msg_callbacks[msg_id]; - return callbacks; + // get callbacks for a specific message + return this._msg_callbacks[msg_id]; }; @@ -407,91 +418,142 @@ var IPython = (function (IPython) { delete this._msg_callbacks[msg_id]; } }; - - + + /* Set callbacks for a particular message. + * Callbacks should be a struct of the following form: + * shell : { + * + * } + + */ Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) { - this._msg_callbacks[msg_id] = callbacks || {}; + if (callbacks) { + // shallow-copy mapping, because we will modify it at the top level + var cbcopy = this._msg_callbacks[msg_id] = {}; + cbcopy.shell = callbacks.shell; + cbcopy.iopub = callbacks.iopub; + cbcopy.input = callbacks.input; + this._msg_callbacks[msg_id] = cbcopy; + } }; Kernel.prototype._handle_shell_reply = function (e) { var reply = $.parseJSON(e.data); $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply}); - var header = reply.header; var content = reply.content; var metadata = reply.metadata; - var msg_type = header.msg_type; - var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id); - if (callbacks !== undefined) { - var cb = callbacks[msg_type]; - if (cb !== undefined) { - cb(content, metadata); - } - }; - - if (content.payload !== undefined) { - var payload = content.payload || []; - this._handle_payload(callbacks, payload); + var parent_id = reply.parent_header.msg_id; + var callbacks = this.get_callbacks_for_msg(parent_id); + if (!callbacks || !callbacks.shell) { + return; + } + var shell_callbacks = callbacks.shell; + + // clear callbacks on shell + delete callbacks.shell; + delete callbacks.input; + if (!callbacks.iopub) { + this.clear_callbacks_for_msg(parent_id); + } + + if (shell_callbacks.reply !== undefined) { + shell_callbacks.reply(reply); + } + if (content.payload && shell_callbacks.payload) { + this._handle_payloads(content.payload, shell_callbacks.payload, reply); } }; - Kernel.prototype._handle_payload = function (callbacks, payload) { - var l = payload.length; + Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) { + var l = payloads.length; // Payloads are handled by triggering events because we don't want the Kernel // to depend on the Notebook or Pager classes. for (var i=0; i= 0) { - var cb = callbacks['output']; - if (cb !== undefined) { - cb(msg_type, content, metadata); - } - } else if (msg_type === 'status') { - if (content.execution_state === 'busy') { - $([IPython.events]).trigger('status_busy.Kernel', {kernel: this}); - } else if (content.execution_state === 'idle') { - $([IPython.events]).trigger('status_idle.Kernel', {kernel: this}); - } else if (content.execution_state === 'restarting') { - // autorestarting is distinct from restarting, - // in that it means the kernel died and the server is restarting it. - // status_restarting sets the notification widget, - // autorestart shows the more prominent dialog. - $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this}); - $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this}); - } else if (content.execution_state === 'dead') { - this.stop_channels(); - $([IPython.events]).trigger('status_dead.Kernel', {kernel: this}); - }; - } else if (msg_type === 'clear_output') { - var cb = callbacks['clear_output']; - if (cb !== undefined) { - cb(content, metadata); - } - }; + var callback = callbacks.iopub.output; + if (callback) { + callback(msg); + } + }; + + // dispatch IOPub messages to respective handlers. + // each message type should have a handler. + Kernel.prototype._handle_iopub_message = function (e) { + var msg = $.parseJSON(e.data); + + var handler = this.get_iopub_handler(msg.header.msg_type); + if (handler !== undefined) { + handler(msg); + } }; @@ -506,12 +568,11 @@ var IPython = (function (IPython) { return; } var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id); - if (callbacks !== undefined) { - var cb = callbacks[msg_type]; - if (cb !== undefined) { - cb(content, metadata); + if (callbacks) { + if (callbacks.input) { + callbacks.input(request); } - }; + } }; diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html index 83b219920..ca27a7fcf 100644 --- a/IPython/html/templates/notebook.html +++ b/IPython/html/templates/notebook.html @@ -232,6 +232,9 @@ class="notebook_app" + + + @@ -240,8 +243,6 @@ class="notebook_app" - - diff --git a/examples/tests/Test Output Callbacks.ipynb b/examples/tests/Test Output Callbacks.ipynb new file mode 100644 index 000000000..ce2bfe87b --- /dev/null +++ b/examples/tests/Test Output Callbacks.ipynb @@ -0,0 +1,269 @@ +{ + "metadata": { + "name": "" + }, + "nbformat": 3, + "nbformat_minor": 0, + "worksheets": [ + { + "cells": [ + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Basic Output" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "from IPython.display import display" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "print 'hi'" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "display('hi')" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "1" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%matplotlib inline\n", + "import matplotlib.pyplot as plt\n", + "plt.plot([1,3,2])" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%javascript\n", + "console.log(\"I ran!\");" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%html\n", + "bold" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%latex\n", + "$$\n", + "a = 5\n", + "$$" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "input_request" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "raw_input(\"prompt > \")" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "set_next_input" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%%writefile tst.py\n", + "def foo():\n", + " pass\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "%load tst.py" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "Pager in execute_reply" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "plt?" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "object_info" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# press tab after parentheses\n", + "int(" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "complete" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "# pres tab after f\n", + "f" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "heading", + "level": 1, + "metadata": {}, + "source": [ + "clear_output" + ] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "import sys\n", + "from IPython.display import clear_output" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "for i in range(10):\n", + " clear_output()\n", + " time.sleep(0.25)\n", + " print i\n", + " sys.stdout.flush()\n", + " time.sleep(0.25)\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "for i in range(10):\n", + " clear_output(wait=True)\n", + " time.sleep(0.25)\n", + " print i\n", + " sys.stdout.flush()\n" + ], + "language": "python", + "metadata": {}, + "outputs": [] + }, + { + "cell_type": "code", + "collapsed": false, + "input": [], + "language": "python", + "metadata": {}, + "outputs": [] + } + ], + "metadata": {} + } + ] +} \ No newline at end of file