From f4b937767ebef3672ef8c074e56d5ed625ce2dbb Mon Sep 17 00:00:00 2001 From: MinRK Date: Mon, 18 Mar 2013 17:45:36 -0700 Subject: [PATCH 01/12] share code between zmq channel handlers --- IPython/frontend/html/notebook/handlers.py | 115 +++++++++------------ 1 file changed, 51 insertions(+), 64 deletions(-) diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py index 7848c1dd7..31a56a4bf 100644 --- a/IPython/frontend/html/notebook/handlers.py +++ b/IPython/frontend/html/notebook/handlers.py @@ -519,23 +519,28 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): self.on_message = self.save_on_message -class IOPubHandler(AuthenticatedZMQStreamHandler): - +class ZMQChannelHandler(AuthenticatedZMQStreamHandler): + + @property + def max_msg_size(self): + return self.settings.get('max_msg_size', 65535) + + def create_stream(self): + km = self.kernel_manager + meth = getattr(km, 'connect_%s' % self.channel) + self.zmq_stream = meth(self.kernel_id) + def initialize(self, *args, **kwargs): - self.iopub_stream = None - + self.zmq_stream = None + def on_first_message(self, msg): try: - super(IOPubHandler, self).on_first_message(msg) + super(ZMQChannelHandler, self).on_first_message(msg) except web.HTTPError: self.close() return - km = self.kernel_manager - kernel_id = self.kernel_id - km.add_restart_callback(kernel_id, self.on_kernel_restarted) - km.add_restart_callback(kernel_id, self.on_restart_failed, 'dead') try: - self.iopub_stream = km.connect_iopub(kernel_id) + self.create_stream() except web.HTTPError: # WebSockets don't response to traditional error codes so we # close the connection. @@ -543,29 +548,32 @@ class IOPubHandler(AuthenticatedZMQStreamHandler): self.stream.close() self.close() else: - self.iopub_stream.on_recv(self._on_zmq_reply) + self.zmq_stream.on_recv(self._on_zmq_reply) def on_message(self, msg): - pass - - def _send_status_message(self, status): - msg = self.session.msg("status", - {'execution_state': status} - ) - self.write_message(jsonapi.dumps(msg, default=date_default)) - - def on_kernel_restarted(self): - self.log.warn("kernel %s restarted", self.kernel_id) - self._send_status_message('restarting') - - def on_restart_failed(self): - self.log.error("kernel %s restarted failed!", self.kernel_id) - self._send_status_message('dead') + if len(msg) < self.max_msg_size: + msg = jsonapi.loads(msg) + self.session.send(self.zmq_stream, msg) def on_close(self): # This method can be called twice, once by self.kernel_died and once # from the WebSocket close event. If the WebSocket connection is # closed before the ZMQ streams are setup, they could be None. + if self.zmq_stream is not None and not self.zmq_stream.closed(): + self.zmq_stream.on_recv(None) + self.zmq_stream.close() + + +class IOPubHandler(ZMQChannelHandler): + channel = 'iopub' + + def create_stream(self): + super(IOPubHandler, self).create_stream() + km = self.kernel_manager + km.add_restart_callback(self.kernel_id, self.on_kernel_restarted) + km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead') + + def on_close(self): km = self.kernel_manager if self.kernel_id in km: km.remove_restart_callback( @@ -574,48 +582,27 @@ class IOPubHandler(AuthenticatedZMQStreamHandler): km.remove_restart_callback( self.kernel_id, self.on_restart_failed, 'dead', ) - if self.iopub_stream is not None and not self.iopub_stream.closed(): - self.iopub_stream.on_recv(None) - self.iopub_stream.close() - - -class ShellHandler(AuthenticatedZMQStreamHandler): + super(IOPubHandler, self).on_close() - @property - def max_msg_size(self): - return self.settings.get('max_msg_size', 65535) - - def initialize(self, *args, **kwargs): - self.shell_stream = None + def _send_status_message(self, status): + msg = self.session.msg("status", + {'execution_state': status} + ) + self.write_message(jsonapi.dumps(msg, default=date_default)) - def on_first_message(self, msg): - try: - super(ShellHandler, self).on_first_message(msg) - except web.HTTPError: - self.close() - return - km = self.kernel_manager - kernel_id = self.kernel_id - try: - self.shell_stream = km.connect_shell(kernel_id) - except web.HTTPError: - # WebSockets don't response to traditional error codes so we - # close the connection. - if not self.stream.closed(): - self.stream.close() - self.close() - else: - self.shell_stream.on_recv(self._on_zmq_reply) + def on_kernel_restarted(self): + logging.warn("kernel %s restarted", self.kernel_id) + self._send_status_message('restarting') - def on_message(self, msg): - if len(msg) < self.max_msg_size: - msg = jsonapi.loads(msg) - self.session.send(self.shell_stream, msg) + def on_restart_failed(self): + logging.error("kernel %s restarted failed!", self.kernel_id) + self._send_status_message('dead') + +class ShellHandler(ZMQChannelHandler): + channel = 'shell' - def on_close(self): - # Make sure the stream exists and is not already closed. - if self.shell_stream is not None and not self.shell_stream.closed(): - self.shell_stream.close() +class StdinHandler(ZMQChannelHandler): + channel = 'stdin' #----------------------------------------------------------------------------- From 510897f0aaf1ec1dbd8e541b2ee7378cf9c719c7 Mon Sep 17 00:00:00 2001 From: MinRK Date: Mon, 18 Mar 2013 17:55:03 -0700 Subject: [PATCH 02/12] add stdin channel to NotebookApp --- IPython/frontend/html/notebook/notebookapp.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py index c4035b423..3ed360324 100644 --- a/IPython/frontend/html/notebook/notebookapp.py +++ b/IPython/frontend/html/notebook/notebookapp.py @@ -66,7 +66,7 @@ from IPython.frontend.html.notebook import DEFAULT_STATIC_FILES_PATH from .kernelmanager import MappingKernelManager from .handlers import (LoginHandler, LogoutHandler, ProjectDashboardHandler, NewHandler, NamedNotebookHandler, - MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, + MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, StdinHandler, ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler, RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler, MainClusterHandler, ClusterProfileHandler, ClusterActionHandler, @@ -160,6 +160,7 @@ class NotebookWebApplication(web.Application): (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler), (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler), (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler), + (r"/kernels/%s/stdin" % _kernel_id_regex, StdinHandler), (r"/notebooks", NotebookRootHandler), (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler), (r"/rstservice/render", RSTHandler), From 74f89725056a477bf528f46ea8019301636ddf72 Mon Sep 17 00:00:00 2001 From: MinRK Date: Mon, 18 Mar 2013 21:55:42 -0700 Subject: [PATCH 03/12] specify socket identity from kernel.js required for stdin routing --- IPython/frontend/html/notebook/handlers.py | 8 +++++++- IPython/frontend/html/notebook/static/js/kernel.js | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py index 31a56a4bf..83796911b 100644 --- a/IPython/frontend/html/notebook/handlers.py +++ b/IPython/frontend/html/notebook/handlers.py @@ -506,6 +506,12 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): # Cookie constructor doesn't accept unicode strings # under Python 2.x for some reason msg = msg.encode('utf8', 'replace') + try: + bsession, msg = msg.split(':', 1) + self.session.session = bsession.decode('ascii') + except Exception: + logging.error("No bsession!", exc_info=True) + pass try: self.request._cookies = Cookie.SimpleCookie(msg) except: @@ -528,7 +534,7 @@ class ZMQChannelHandler(AuthenticatedZMQStreamHandler): def create_stream(self): km = self.kernel_manager meth = getattr(km, 'connect_%s' % self.channel) - self.zmq_stream = meth(self.kernel_id) + self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession) def initialize(self, *args, **kwargs): self.zmq_stream = None diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js index ee304db4a..78741c7f6 100644 --- a/IPython/frontend/html/notebook/static/js/kernel.js +++ b/IPython/frontend/html/notebook/static/js/kernel.js @@ -129,7 +129,7 @@ var IPython = (function (IPython) { this.shell_channel = new this.WebSocket(ws_url + "/shell"); this.iopub_channel = new this.WebSocket(ws_url + "/iopub"); send_cookie = function(){ - this.send(document.cookie); + this.send(that.session_id + ':' + document.cookie); }; var already_called_onclose = false; // only alert once var ws_closed_early = function(evt){ From f38f2b7ac4ba0dff61c87badbe8b033755f1464d Mon Sep 17 00:00:00 2001 From: MinRK Date: Mon, 18 Mar 2013 21:57:35 -0700 Subject: [PATCH 04/12] add stdin to notebook dumb / gross / ugly jQuery modal dialog for now, but it works! --- .../html/notebook/static/js/kernel.js | 71 +++++++++++++------ .../html/notebook/static/js/notebook.js | 28 ++++++++ 2 files changed, 77 insertions(+), 22 deletions(-) diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js index 78741c7f6..4bd1cfd7a 100644 --- a/IPython/frontend/html/notebook/static/js/kernel.js +++ b/IPython/frontend/html/notebook/static/js/kernel.js @@ -28,6 +28,7 @@ var IPython = (function (IPython) { this.kernel_id = null; this.shell_channel = null; this.iopub_channel = null; + this.stdin_channel = null; this.base_url = base_url; this.running = false; this.username = "username"; @@ -127,6 +128,7 @@ var IPython = (function (IPython) { var ws_url = this.ws_url + this.kernel_url; console.log("Starting WebSockets:", ws_url); this.shell_channel = new this.WebSocket(ws_url + "/shell"); + this.stdin_channel = new this.WebSocket(ws_url + "/stdin"); this.iopub_channel = new this.WebSocket(ws_url + "/iopub"); send_cookie = function(){ this.send(that.session_id + ':' + document.cookie); @@ -150,21 +152,22 @@ var IPython = (function (IPython) { that._websocket_closed(ws_url, false); } }; - this.shell_channel.onopen = send_cookie; - this.shell_channel.onclose = ws_closed_early; - this.iopub_channel.onopen = send_cookie; - this.iopub_channel.onclose = ws_closed_early; + var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel]; + for (var i=0; i < channels.length; i++) { + channels[i].onopen = send_cookie; + channels[i].onclose = ws_closed_early; + } // switch from early-close to late-close message after 1s setTimeout(function() { - if (that.shell_channel !== null) { - that.shell_channel.onclose = ws_closed_late; - } - if (that.iopub_channel !== null) { - that.iopub_channel.onclose = ws_closed_late; + for (var i=0; i < channels.length; i++) { + if (channels[i] !== null) { + channels[i].onclose = ws_closed_late; + } } }, 1000); - this.shell_channel.onmessage = $.proxy(this._handle_shell_reply,this); - this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply,this); + this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this); + this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this); + this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this); }; /** @@ -172,16 +175,14 @@ var IPython = (function (IPython) { * @method stop_channels */ Kernel.prototype.stop_channels = function () { - if (this.shell_channel !== null) { - this.shell_channel.onclose = function (evt) {}; - this.shell_channel.close(); - this.shell_channel = null; - }; - if (this.iopub_channel !== null) { - this.iopub_channel.onclose = function (evt) {}; - this.iopub_channel.close(); - this.iopub_channel = null; + 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].close(); + } }; + this.shell_channel = this.iopub_channel = this.stdin_channel = null; }; // Main public methods. @@ -282,7 +283,7 @@ var IPython = (function (IPython) { silent : true, user_variables : [], user_expressions : {}, - allow_stdin : false + allow_stdin : true }; $.extend(true, content, options) $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content}); @@ -343,8 +344,19 @@ var IPython = (function (IPython) { }; }; + Kernel.prototype.send_input_reply = function (input, header) { - // Reply handlers. + var content = { + value : input, + }; + $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content}); + var msg = this._get_msg("input_reply", content); + this.stdin_channel.send(JSON.stringify(msg)); + return msg.header.msg_id; + }; + + + // Reply handlers Kernel.prototype.get_callbacks_for_msg = function (msg_id) { var callbacks = this._msg_callbacks[msg_id]; @@ -433,6 +445,21 @@ var IPython = (function (IPython) { }; + Kernel.prototype._handle_input_request = function (e) { + var request = $.parseJSON(e.data); + console.log("input", request); + var header = request.header; + var content = request.content; + var metadata = request.metadata; + var msg_type = header.msg_type; + if (msg_type !== 'input_request') { + console.log("Invalid input request!", request); + return; + } + $([IPython.events]).trigger('input_request.Kernel', {kernel: this, request:request}); + }; + + IPython.Kernel = Kernel; return IPython; diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index 52c50cd43..b81109dd8 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -115,6 +115,34 @@ var IPython = (function (IPython) { var index = that.find_cell_index(data.cell); that.select(index); }); + $([IPython.events]).on('input_request.Kernel', function (event, data) { + var dialog = $('
').attr('id','input_form').append( + $('
') + .attr("action", "javascript:$('#input_form').parent().find('button').click();") + .append( + $('') + .attr('id', 'input_prompt_dialog') + .attr('type', 'text') + .attr('name', 'input') + )); + $(document).append(dialog); + dialog.dialog({ + resizable: false, + modal: true, + title: data.request.content.prompt, + closeText: '', + buttons : { + "Okay": function () { + IPython.notebook.kernel.send_input_reply( + $("input#input_prompt_dialog").attr('value'), + data.request.header + ); + $(this).dialog('close'); + dialog.remove(); + } + } + }); + }); $(document).keydown(function (event) { From 5b434b705f854c3858707a3c4240d49f79679266 Mon Sep 17 00:00:00 2001 From: MinRK Date: Tue, 19 Mar 2013 11:49:04 -0700 Subject: [PATCH 05/12] use inline raw_input instead of a dialog --- .../html/notebook/static/js/codecell.js | 16 ++++++- .../html/notebook/static/js/kernel.js | 21 +++++++-- .../html/notebook/static/js/notebook.js | 28 ----------- .../html/notebook/static/js/outputarea.js | 46 +++++++++++++++++++ .../html/notebook/static/less/notebook.less | 8 ++++ 5 files changed, 85 insertions(+), 34 deletions(-) diff --git a/IPython/frontend/html/notebook/static/js/codecell.js b/IPython/frontend/html/notebook/static/js/codecell.js index a280ed2a4..6ce4e4f6c 100644 --- a/IPython/frontend/html/notebook/static/js/codecell.js +++ b/IPython/frontend/html/notebook/static/js/codecell.js @@ -245,7 +245,8 @@ var IPython = (function (IPython) { '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) + 'set_next_input': $.proxy(this._handle_set_next_input, this), + 'input_request': $.proxy(this._handle_input_request, this) }; var msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false}); }; @@ -260,10 +261,23 @@ var IPython = (function (IPython) { $([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} $([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); + } + // Basic cell manipulation. diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js index 4bd1cfd7a..162764aeb 100644 --- a/IPython/frontend/html/notebook/static/js/kernel.js +++ b/IPython/frontend/html/notebook/static/js/kernel.js @@ -168,6 +168,10 @@ var IPython = (function (IPython) { this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this); this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this); this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this); + + $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) { + that.send_input_reply(data); + }); }; /** @@ -283,8 +287,11 @@ var IPython = (function (IPython) { silent : true, user_variables : [], user_expressions : {}, - allow_stdin : true + allow_stdin : false }; + if (callbacks.input_request !== undefined) { + content.allow_stdin = true; + } $.extend(true, content, options) $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content}); var msg = this._get_msg("execute_request", content); @@ -344,8 +351,7 @@ var IPython = (function (IPython) { }; }; - Kernel.prototype.send_input_reply = function (input, header) { - + Kernel.prototype.send_input_reply = function (input) { var content = { value : input, }; @@ -447,7 +453,6 @@ var IPython = (function (IPython) { Kernel.prototype._handle_input_request = function (e) { var request = $.parseJSON(e.data); - console.log("input", request); var header = request.header; var content = request.content; var metadata = request.metadata; @@ -456,7 +461,13 @@ var IPython = (function (IPython) { console.log("Invalid input request!", request); return; } - $([IPython.events]).trigger('input_request.Kernel', {kernel: this, request:request}); + 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); + } + }; }; diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index b81109dd8..52c50cd43 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -115,34 +115,6 @@ var IPython = (function (IPython) { var index = that.find_cell_index(data.cell); that.select(index); }); - $([IPython.events]).on('input_request.Kernel', function (event, data) { - var dialog = $('
').attr('id','input_form').append( - $('') - .attr("action", "javascript:$('#input_form').parent().find('button').click();") - .append( - $('') - .attr('id', 'input_prompt_dialog') - .attr('type', 'text') - .attr('name', 'input') - )); - $(document).append(dialog); - dialog.dialog({ - resizable: false, - modal: true, - title: data.request.content.prompt, - closeText: '', - buttons : { - "Okay": function () { - IPython.notebook.kernel.send_input_reply( - $("input#input_prompt_dialog").attr('value'), - data.request.header - ); - $(this).dialog('close'); - dialog.remove(); - } - } - }); - }); $(document).keydown(function (event) { diff --git a/IPython/frontend/html/notebook/static/js/outputarea.js b/IPython/frontend/html/notebook/static/js/outputarea.js index 5a11dd285..18c8c8f30 100644 --- a/IPython/frontend/html/notebook/static/js/outputarea.js +++ b/IPython/frontend/html/notebook/static/js/outputarea.js @@ -74,6 +74,7 @@ var IPython = (function (IPython) { OutputArea.prototype.bind_events = function () { var that = this; + this._submit_raw_input_proxy = $.proxy(this._submit_raw_input, this); this.prompt_overlay.dblclick(function () { that.toggle_output(); }); this.prompt_overlay.click(function () { that.toggle_scroll(); }); @@ -448,6 +449,51 @@ var IPython = (function (IPython) { toinsert.append(latex); element.append(toinsert); }; + + OutputArea.prototype.append_raw_input = function (content) { + this.expand(); + this.flush_clear_timeout(); + var area = this.create_output_area(); + area.append( + $("
") + .addClass("box-flex1 output_subarea raw_input") + .append( + $("") + .attr("action", "javascript:$([IPython.events]).trigger('submit_raw_input.OutputArea');") + .append( + $("") + .addClass("input_prompt") + .text(content.prompt) + ).append( + $("") + .attr("size", 80) + .addClass("raw_input") + ) + ) + ) + // clear events first + $([IPython.events]).off('submit_raw_input.OutputArea'); + $([IPython.events]).on('submit_raw_input.OutputArea', this._submit_raw_input_proxy); + this.element.append(area); + area.find("input.raw_input").focus(); + } + OutputArea.prototype._submit_raw_input = function (evt) { + var container = this.element.find("div.raw_input"); + var theprompt = container.find("span.input_prompt"); + var theinput = container.find("input.raw_input"); + var value = theinput.attr("value"); + var content = { + output_type : 'stream', + name : 'stdout', + text : theprompt.text() + value + '\n' + } + // remove form container + container.parent().remove(); + // replace with plaintext version in stdout + this.append_output(content, false); + $([IPython.events]).off('submit_raw_input.OutputArea', this._submit_raw_input_proxy); + $([IPython.events]).trigger('send_input_reply.Kernel', value); + } OutputArea.prototype.handle_clear_output = function (content) { diff --git a/IPython/frontend/html/notebook/static/less/notebook.less b/IPython/frontend/html/notebook/static/less/notebook.less index eb88ea0b6..4d1cc9ce9 100644 --- a/IPython/frontend/html/notebook/static/less/notebook.less +++ b/IPython/frontend/html/notebook/static/less/notebook.less @@ -476,4 +476,12 @@ margin-bottom:0; a.heading-anchor:link, a.heading-anchor:visited { text-decoration: none; color: inherit; + +/* raw_input styles */ + +span.input_prompt { + font-family: monospace; +} +input.raw_input { + width: auto; } From f09eaf7b5e123290eb66ac2ee8959463b2f32b21 Mon Sep 17 00:00:00 2001 From: MinRK Date: Fri, 5 Apr 2013 18:51:08 -0700 Subject: [PATCH 06/12] cleanup stdin event submission follow example in rename notebook: remove form, bind keydown for enter, avoiding shift-enter submitting the cell again. --- .../html/notebook/static/js/outputarea.js | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/IPython/frontend/html/notebook/static/js/outputarea.js b/IPython/frontend/html/notebook/static/js/outputarea.js index 18c8c8f30..432939156 100644 --- a/IPython/frontend/html/notebook/static/js/outputarea.js +++ b/IPython/frontend/html/notebook/static/js/outputarea.js @@ -74,7 +74,6 @@ var IPython = (function (IPython) { OutputArea.prototype.bind_events = function () { var that = this; - this._submit_raw_input_proxy = $.proxy(this._submit_raw_input, this); this.prompt_overlay.dblclick(function () { that.toggle_output(); }); this.prompt_overlay.click(function () { that.toggle_scroll(); }); @@ -451,29 +450,34 @@ var IPython = (function (IPython) { }; OutputArea.prototype.append_raw_input = function (content) { + var that = this; this.expand(); this.flush_clear_timeout(); var area = this.create_output_area(); + area.append( $("
") .addClass("box-flex1 output_subarea raw_input") .append( - $("") - .attr("action", "javascript:$([IPython.events]).trigger('submit_raw_input.OutputArea');") - .append( - $("") - .addClass("input_prompt") - .text(content.prompt) - ).append( - $("") - .attr("size", 80) - .addClass("raw_input") - ) + $("") + .addClass("input_prompt") + .text(content.prompt) + ) + .append( + $("") + .addClass("raw_input") + .attr('type', 'text') + .attr("size", 80) + .keydown(function (event, ui) { + // make sure we submit on enter, + // and don't re-execute the *cell* on shift-enter + if (event.which === utils.keycodes.ENTER) { + that._submit_raw_input(); + return false; + } + }) ) - ) - // clear events first - $([IPython.events]).off('submit_raw_input.OutputArea'); - $([IPython.events]).on('submit_raw_input.OutputArea', this._submit_raw_input_proxy); + ); this.element.append(area); area.find("input.raw_input").focus(); } @@ -491,7 +495,6 @@ var IPython = (function (IPython) { container.parent().remove(); // replace with plaintext version in stdout this.append_output(content, false); - $([IPython.events]).off('submit_raw_input.OutputArea', this._submit_raw_input_proxy); $([IPython.events]).trigger('send_input_reply.Kernel', value); } From 31c8fb1fb6306117b17c41701d9aa0ad9ff5919b Mon Sep 17 00:00:00 2001 From: MinRK Date: Fri, 5 Apr 2013 18:51:35 -0700 Subject: [PATCH 07/12] tweak raw-input styling should fix alignment of raw_input and prompt --- IPython/frontend/html/notebook/static/less/notebook.less | 3 +++ 1 file changed, 3 insertions(+) diff --git a/IPython/frontend/html/notebook/static/less/notebook.less b/IPython/frontend/html/notebook/static/less/notebook.less index 4d1cc9ce9..7f7619734 100644 --- a/IPython/frontend/html/notebook/static/less/notebook.less +++ b/IPython/frontend/html/notebook/static/less/notebook.less @@ -484,4 +484,7 @@ span.input_prompt { } input.raw_input { width: auto; + height: 1em; + line-height: 1em; + margin: -2px 0px 0px 0px; } From 3fbb99e61ae61186f651210625971c49a1abea68 Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 25 Apr 2013 16:07:51 -0700 Subject: [PATCH 08/12] add no-op on_message for iopub --- IPython/frontend/html/notebook/handlers.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py index 83796911b..dd0ac567a 100644 --- a/IPython/frontend/html/notebook/handlers.py +++ b/IPython/frontend/html/notebook/handlers.py @@ -604,6 +604,10 @@ class IOPubHandler(ZMQChannelHandler): logging.error("kernel %s restarted failed!", self.kernel_id) self._send_status_message('dead') + def on_message(self, msg): + """IOPub messages make no sense""" + pass + class ShellHandler(ZMQChannelHandler): channel = 'shell' From ba79b60c357b9531d1de7fbb2396272574f6b0b2 Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 25 Apr 2013 16:19:15 -0700 Subject: [PATCH 09/12] fix color in raw_input --- IPython/frontend/html/notebook/static/less/notebook.less | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/frontend/html/notebook/static/less/notebook.less b/IPython/frontend/html/notebook/static/less/notebook.less index 7f7619734..b316ac368 100644 --- a/IPython/frontend/html/notebook/static/less/notebook.less +++ b/IPython/frontend/html/notebook/static/less/notebook.less @@ -481,8 +481,10 @@ a.heading-anchor:link, a.heading-anchor:visited { span.input_prompt { font-family: monospace; + } input.raw_input { + color: inherit; width: auto; height: 1em; line-height: 1em; From 938a5b5dbca6429cd8ffc124ddf069640e3bacee Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 25 Apr 2013 17:25:30 -0700 Subject: [PATCH 10/12] tweak raw_input style more crowded, less jumpy --- .../html/notebook/static/less/notebook.less | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/IPython/frontend/html/notebook/static/less/notebook.less b/IPython/frontend/html/notebook/static/less/notebook.less index b316ac368..c1c56b0b4 100644 --- a/IPython/frontend/html/notebook/static/less/notebook.less +++ b/IPython/frontend/html/notebook/static/less/notebook.less @@ -476,17 +476,27 @@ margin-bottom:0; a.heading-anchor:link, a.heading-anchor:visited { text-decoration: none; color: inherit; +} /* raw_input styles */ -span.input_prompt { +div.raw_input { + padding-top: 0px; + padding-bottom: 0px; + height: 1em; + line-height: 1em; font-family: monospace; - +} +span.input_prompt { + font-family: inherit; } input.raw_input { - color: inherit; - width: auto; - height: 1em; - line-height: 1em; - margin: -2px 0px 0px 0px; + font-family: inherit; + font-size: inherit; + color: inherit; + width: auto; + margin: -2px 0px 0px 1px; + padding-left: 1px; + padding-top: 2px; + height: 1em; } From f51425ed288618bc992d9a688ebfcacce9abee7f Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 25 Apr 2013 20:43:33 -0700 Subject: [PATCH 11/12] clarify first ws message names / messages --- IPython/frontend/html/notebook/handlers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/IPython/frontend/html/notebook/handlers.py b/IPython/frontend/html/notebook/handlers.py index dd0ac567a..d768df994 100644 --- a/IPython/frontend/html/notebook/handlers.py +++ b/IPython/frontend/html/notebook/handlers.py @@ -507,11 +507,11 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler): # under Python 2.x for some reason msg = msg.encode('utf8', 'replace') try: - bsession, msg = msg.split(':', 1) - self.session.session = bsession.decode('ascii') + identity, msg = msg.split(':', 1) + self.session.session = identity.decode('ascii') except Exception: - logging.error("No bsession!", exc_info=True) - pass + logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg) + try: self.request._cookies = Cookie.SimpleCookie(msg) except: From 43eb765793d88328ed0a63b27db54eccf71fcdd7 Mon Sep 17 00:00:00 2001 From: MinRK Date: Thu, 25 Apr 2013 21:05:07 -0700 Subject: [PATCH 12/12] js comment about session id in first message --- IPython/frontend/html/notebook/static/js/kernel.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/IPython/frontend/html/notebook/static/js/kernel.js b/IPython/frontend/html/notebook/static/js/kernel.js index 162764aeb..9a66865bb 100644 --- a/IPython/frontend/html/notebook/static/js/kernel.js +++ b/IPython/frontend/html/notebook/static/js/kernel.js @@ -131,6 +131,8 @@ var IPython = (function (IPython) { this.stdin_channel = new this.WebSocket(ws_url + "/stdin"); this.iopub_channel = new this.WebSocket(ws_url + "/iopub"); send_cookie = function(){ + // send the session id so the Session object Python-side + // has the same identity this.send(that.session_id + ':' + document.cookie); }; var already_called_onclose = false; // only alert once