This feature was discussed in #6123, but it doesn't look like anything was ever incorporated into the IPython Notebook.

Here's a brief overview of the changes:

- Display of messages from other clients can be toggled on and off from within a notebook, either using the ``<M-m>e`` keyboard shortcut in the web UI, or through the option in the "Kernel" menu.
- notebook.js controls whether messages are displayed through a callback that is invoked from kernel.js when no callbacks are available for a message.
- The UI displays ``execute_input`` messages originating from an other clients in new cells at the end of the notebook. Output messages (``execute_result`` et al.) will only be displayed if a cell exists with a matching message ID.

Pending design questions:

- Should each ``execute_input`` message cause a new cell to be created?
- Should new cells be placed at the end of the notebook, or elsewhere? If the latter, what criteria should be followed?
This commit is contained in:
Nathan Heijermans 2014-12-02 21:32:43 -05:00
parent a19f345170
commit 9c2afc9620
7 changed files with 97 additions and 0 deletions

View File

@ -253,6 +253,14 @@ define(['require'
env.notebook.delete_cell();
}
},
'toggle-unsolicited-message-display':{
help: 'toggle display from external clients',
icon: 'fa-sitemap',
help_index: 'gb',
handler: function (env) {
env.notebook.toggle_ignore_unsolicited_msgs();
}
},
'interrupt-kernel':{
icon: 'fa-stop',
help_index : 'ha',

View File

@ -95,6 +95,7 @@ define([
'space' : 'ipython.scroll-down',
'down' : 'ipython.select-next-cell',
'i,i' : 'ipython.interrupt-kernel',
'e': 'ipython.toggle-unsolicited-message-display',
'0,0' : 'ipython.restart-kernel',
'd,d' : 'ipython.delete-cell',
'esc': 'ipython.close-pager',

View File

@ -278,6 +278,9 @@ define([
});
// Kernel
this.element.find('#toggle_unsolicited').click(function() {
IPython.notebook.toggle_ignore_unsolicited_msgs();
});
this.element.find('#int_kernel').click(function () {
that.notebook.kernel.interrupt();
});

View File

@ -135,6 +135,7 @@ define([
this.undelete_below = false;
this.paste_enabled = false;
this.writable = false;
this.ignore_unsolicited_msgs = false;
// It is important to start out in command mode to match the intial mode
// of the KeyboardManager.
this.mode = 'command';
@ -1562,6 +1563,60 @@ define([
this.get_selected_cell().toggle_line_numbers();
};
// Support for displaying input and output messages from other iPy clients.
/**
* Toggles the ability to display input/output message events from
* externally connected clients (i.e. other iPython shells, vim-ipython,
* etc).
*
* @method toggle_ignore_unsolicited_msgs
*/
Notebook.prototype.toggle_ignore_unsolicited_msgs = function () {
this.ignore_unsolicited_msgs = !this.ignore_unsolicited_msgs;
this.events.trigger('toggle_unsolicited_msgs.Notebook',
[this.ignore_unsolicited_msgs]);
return this.ignore_unsolicited_msgs;
};
/**
* Handles the display of unsolicited messages, i.e. inputs or outputs that
* were generated by a client other than this notebook. New messages are
* displayed at the bottom of the notebook.
*
* @method handle_unsolicited_msg
*/
Notebook.prototype.handle_unsolicited_msg = function(msg) {
if (this.ignore_unsolicited_msgs) {
return;
}
if (msg.msg_type == 'execute_input') {
var cell = this.insert_cell_at_bottom('code');
if (cell) {
var cell_index = this.ncells() - 1;
cell.last_msg_id = msg.parent_header.msg_id;
cell.set_text(msg.content.code);
cell._handle_execute_reply(msg);
this.scroll_to_cell(cell_index);
this.select(cell_index);
}
} else {
/* Find the input cell that corresponds with the output, then add
* the contents to the cell's output area.
*/
var count = this.ncells();
while (count--) {
var cell = this.get_cell(count);
if (cell && cell.last_msg_id == msg.parent_header.msg_id) {
cell.output_area.handle_output(msg);
this.scroll_to_cell(count);
this.select(count);
break;
}
}
}
};
/**
* Set the codemirror mode for all code cells, including the default for
* new code cells.
@ -1639,6 +1694,7 @@ define([
cell.set_kernel(this.session.kernel);
}
}
this.kernel.unsolicited_msg_callback = $.proxy(this.handle_unsolicited_msg, this);
};
Notebook.prototype._session_start_failed = function (jqxhr, status, error){
this._session_starting = false;

View File

@ -279,6 +279,11 @@ define([
nnw.warning(error.message || "Notebook copy failed");
});
this.events.on('toggle_unsolicited_msgs.Notebook', function(evt, ignored) {
var msg = (ignored? "Ignoring": "Showing") + " I/O from external clients";
nnw.set_message(msg, 1000);
});
// Checkpoint events
this.events.on('checkpoint_created.Notebook', function (evt, data) {
var msg = "Checkpoint created";

View File

@ -47,6 +47,7 @@ define([
this.session_id = utils.uuid();
this._msg_callbacks = {};
this.info_reply = {}; // kernel_info_reply stored here after starting
this.unsolicited_msg_callback = null;
if (typeof(WebSocket) !== 'undefined') {
this.WebSocket = WebSocket;
@ -136,6 +137,7 @@ define([
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));
this.register_iopub_handler('execute_input', $.proxy(this._handle_input_message, this));
for (var i=0; i < output_msg_types.length; i++) {
this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
@ -994,6 +996,11 @@ define([
Kernel.prototype._handle_output_message = function (msg) {
var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
if (!callbacks || !callbacks.iopub) {
if (this.unsolicited_msg_callback) {
// The message came from another client. Let the UI decide what
// to do with it.
this.unsolicited_msg_callback(msg);
}
return;
}
var callback = callbacks.iopub.output;
@ -1002,6 +1009,20 @@ define([
}
};
/**
* Handle an input message (execute_input).
*
* @function _handle_input message
*/
Kernel.prototype._handle_input_message = function (msg) {
var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
if (!callbacks && this.unsolicited_msg_callback) {
// The message came from another client. Let the UI decide what to
// do with it.
this.unsolicited_msg_callback(msg);
}
};
/**
* Dispatch IOPub messages to respective handlers. Each message
* type should have a handler.

View File

@ -228,6 +228,9 @@ class="notebook_app"
</li>
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
<ul id="kernel_menu" class="dropdown-menu">
<li id="toggle_unsolicited"
title="Toggle display of unsolicited messages.">
<a href="#">Show/ignore unsolicited messages.</a></li>
<li id="int_kernel"
title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
<a href="#">Interrupt</a>