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