From 0bfc7544fae7d4776c1bf00c332ce53c725d72a4 2011-07-21 03:42:31 From: Brian Granger Date: 2011-07-21 03:42:31 Subject: [PATCH] Initial reply handling implemented along with css fixes. --- diff --git a/IPython/frontend/html/notebook/kernelmanager.py b/IPython/frontend/html/notebook/kernelmanager.py index 48f61a0..f4d19c1 100644 --- a/IPython/frontend/html/notebook/kernelmanager.py +++ b/IPython/frontend/html/notebook/kernelmanager.py @@ -33,7 +33,7 @@ class KernelManager(object): def start_kernel(self, kernel_id): if kernel_id in self._kernels: raise DuplicateKernelError("Kernel already exists: %s" % kernel_id) - (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel() + (process, shell_port, iopub_port, stdin_port, hb_port) = launch_kernel(pylab='inline') d = dict( process = process, stdin_port = stdin_port, diff --git a/IPython/frontend/html/notebook/notebook.py b/IPython/frontend/html/notebook/notebook.py index 7d3f554..0cc8725 100644 --- a/IPython/frontend/html/notebook/notebook.py +++ b/IPython/frontend/html/notebook/notebook.py @@ -80,15 +80,17 @@ class ZMQStreamHandler(websocket.WebSocketHandler, BaseKernelHandler): self.zmq_stream.on_recv(self._on_zmq_reply) def on_message(self, msg): - logging.info("Message received: %r" % msg) + logging.info("Message received: %r, %r" % (msg, self.__class__)) + logging.info(self.zmq_stream) self.zmq_stream.send_unicode(msg) def on_close(self): self.zmq_stream.close() - def _on_zmq_reply(self, msg): - logging.info("Message reply: %r" % msg) - self.write_message(msg) + def _on_zmq_reply(self, msg_list): + for msg in msg_list: + logging.info("Message reply: %r" % msg) + self.write_message(msg) class IOPubStreamHandler(ZMQStreamHandler): diff --git a/IPython/frontend/html/notebook/session.py b/IPython/frontend/html/notebook/session.py index 5fe5160..ccce092 100644 --- a/IPython/frontend/html/notebook/session.py +++ b/IPython/frontend/html/notebook/session.py @@ -33,6 +33,7 @@ class SessionManager(object): session_id = str(uuid.uuid4()) ports = self.kernel_manager.get_kernel_ports(self.kernel_id) iopub_stream = self.create_connected_stream(ports['iopub_port'], zmq.SUB) + iopub_stream.socket.setsockopt(zmq.SUBSCRIBE, b'') shell_stream = self.create_connected_stream(ports['shell_port'], zmq.XREQ) self._sessions[session_id] = dict( iopub_stream = iopub_stream, @@ -54,7 +55,7 @@ class SessionManager(object): def create_connected_stream(self, port, socket_type): sock = self.context.socket(socket_type) addr = "tcp://%s:%i" % (self.kernel_manager.ip, port) - logging.info("Connecting to: %s" % addr) + logging.info("Connecting to: %s, %r" % (addr, socket_type)) sock.connect(addr) return ZMQStream(sock) diff --git a/IPython/frontend/html/notebook/static/css/notebook.css b/IPython/frontend/html/notebook/static/css/notebook.css index cc5a102..e95f4b0 100644 --- a/IPython/frontend/html/notebook/static/css/notebook.css +++ b/IPython/frontend/html/notebook/static/css/notebook.css @@ -1,24 +1,77 @@ -html, body, div, span, applet, object, iframe, +/** + * HTML5 ✰ Boilerplate + * + * style.css contains a reset, font normalization and some base styles. + * + * Credit is left where credit is due. + * Much inspiration was taken from these projects: + * - yui.yahooapis.com/2.8.1/build/base/base.css + * - camendesign.com/design/ + * - praegnanz.de/weblog/htmlcssjs-kickstart + */ + + +/** + * html5doctor.com Reset Stylesheet (Eric Meyer's Reset Reloaded + HTML5 baseline) + * v1.6.1 2010-09-17 | Authors: Eric Meyer & Richard Clark + * html5doctor.com/html-5-reset-stylesheet/ + */ + +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, -a, abbr, acronym, address, big, cite, code, -del, dfn, em, img, ins, kbd, q, s, samp, -small, strike, strong, sub, sup, tt, var, -b, u, i, center, -dl, dt, dd, ol, ul, li, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, -article, aside, canvas, details, embed, -figure, figcaption, footer, header, hgroup, -menu, nav, output, ruby, section, summary, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, time, mark, audio, video { - margin: 0; - padding: 0; - border: 0; - font-size: 100%; - font: inherit; - vertical-align: baseline; + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; } +blockquote, q { quotes: none; } + +blockquote:before, blockquote:after, +q:before, q:after { content: ""; content: none; } + +ins { background-color: #ff9; color: #000; text-decoration: none; } + +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } + +del { text-decoration: line-through; } + +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } + +table { border-collapse: collapse; border-spacing: 0; } + +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } + +input, select { vertical-align: middle; } + + +/** + * Font normalization inspired by YUI Library's fonts.css: developer.yahoo.com/yui/ + */ + +body { font:13px/1.231 sans-serif; *font-size:small; } /* Hack retained to preserve specificity */ +select, input, textarea, button { font:99% sans-serif; } + +/* Normalize monospace sizing: + en.wikipedia.org/wiki/MediaWiki_talk:Common.css/Archive_11#Teletype_style_fix_for_Chrome */ +pre, code, kbd, samp { font-family: monospace, sans-serif; } + + + body { background-color: white; } @@ -37,45 +90,33 @@ div#toolbar { border-bottom-width: 2px; border-bottom-style: solid; border-bottom-color: black; - padding: 5px; + padding: 5px; } -#main_toolbar { -} -#main_toolbar button { +/*#main_toolbar button { font-size: 0.9em; -} +}*/ div.notebook { - width: 760px; + width: 790px; height: 100%; margin-left: auto; margin-right: auto; padding-top: 5px; padding-bottom: 5px; background-color: white; - -/* Uncomment this block for help in debugging the padding and margins -/* border-left-width: 1px; - border-left-style: solid; - border-left-color: black; - border-right-width: 1px; - border-right-style: solid; - border-right-color: black; - border-bottom-width: 1px; - border-bottom-style: solid; - border-bottom-color: black;*/ } div.cell { width: 740px; - margin: 5px 5px 5px 5px; + margin: 5px auto 5px 5px; padding: 5px; - font-size: 11pt; position: relative; + display: table; } + div.code_cell { background-color: white; } @@ -83,16 +124,18 @@ div.code_cell { div.prompt { vertical-align: top; display: table-cell; - width: 85px; - min-width 80px !important; + width: 80px; + padding: 0px; + margin: 0px; font-family: Menlo, "Courier New", Courier, mono; font-weight: normal; font-style: normal; } div.input { - display: table; - height: auto; + display: table-row; + padding: 0px; + margin: 0px; } div.input_prompt { @@ -106,19 +149,21 @@ textarea.input_area { font-size: inherit; border-style: none; display: table-cell; - margin: 0; - padding: 0; + padding: 0px; + margin: 0px; overflow: auto; font-weight: normal; font-style: normal; - width: 665px; + width: 650px; outline: none; resize: none; } div.output { - display: table; + display: table-row; + padding: 0px; + margin: 0px; } div.output_prompt { @@ -128,11 +173,10 @@ div.output_prompt { div.output_area { text-align: left; font-family: Menlo, "Courier New", Courier, mono; - font-size: inherit; - margin: 0; - padding: 0; + padding: 0px; + margin: 0px; display: table-cell; - width: 665px; + width: 650px; } div.text_cell { diff --git a/IPython/frontend/html/notebook/static/js/notebook.js b/IPython/frontend/html/notebook/static/js/notebook.js index ffdc09f..3cead66 100644 --- a/IPython/frontend/html/notebook/static/js/notebook.js +++ b/IPython/frontend/html/notebook/static/js/notebook.js @@ -2,6 +2,67 @@ var IPYTHON = {}; //============================================================================ +// Utilities +//============================================================================ + + +var uuid = function () { + // http://www.ietf.org/rfc/rfc4122.txt + var s = []; + var hexDigits = "0123456789ABCDEF"; + for (var i = 0; i < 32; i++) { + s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1); + } + s[12] = "4"; // bits 12-15 of the time_hi_and_version field to 0010 + s[16] = hexDigits.substr((s[16] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01 + + var uuid = s.join(""); + return uuid; +}; + + +//Fix raw text to parse correctly in crazy XML +function xmlencode(string) { + return string.replace(/\&/g,'&'+'amp;') + .replace(//g,'&'+'gt;') + .replace(/\'/g,'&'+'apos;') + .replace(/\"/g,'&'+'quot;') + .replace(/`/g,'&'+'#96;') +} + +//Map from terminal commands to CSS classes +attrib = { + "30":"cblack", "31":"cred", + "32":"cgreen", "33":"cyellow", + "34":"cblue", "36":"ccyan", + "37":"cwhite", "01":"cbold"} + +//Fixes escaped console commands, IE colors. Turns them into HTML +function fixConsole(txt) { + txt = xmlencode(txt) + var re = /\033\[([\d;]*?)m/ + var opened = false + var cmds = [] + var opener = "" + var closer = "" + + while (re.test(txt)) { + var cmds = txt.match(re)[1].split(";") + closer = opened?"":"" + opened = cmds.length > 1 || cmds[0] != 0 + var rep = [] + for (var i in cmds) + if (typeof(attrib[cmds[i]]) != "undefined") + rep.push(attrib[cmds[i]]) + opener = rep.length > 0?"":"" + txt = txt.replace(re, closer + opener) + } + if (opened) txt += "" + return txt.trim() +} + +//============================================================================ // Notebook //============================================================================ @@ -11,14 +72,18 @@ var Notebook = function (selector) { this.element.scroll(); this.element.data("notebook", this); this.next_prompt_number = 1; + this.next_kernel_number = 0; + this.kernel = null; + this.msg_cell_map = {}; this.bind_events(); + this.start_kernel(); }; Notebook.prototype.bind_events = function () { var that = this; $(document).keydown(function (event) { - console.log(event); + // console.log(event); if (event.which == 38 && event.shiftKey) { event.preventDefault(); that.select_prev(); @@ -27,9 +92,27 @@ Notebook.prototype.bind_events = function () { that.select_next(); } else if (event.which == 13 && event.shiftKey) { // The focus is not quite working here. + var cell = that.selected_cell(); + var cell_index = that.find_cell_index(cell); + if (cell instanceof CodeCell) { + event.preventDefault(); + cell.clear_output(); + var msg_id = that.kernel.execute(cell.get_code()); + that.msg_cell_map[msg_id] = cell.cell_id; + if (cell_index === (that.ncells()-1)) { + that.insert_code_cell_after(); + } else { + that.select(cell_index+1); + }; + } + } else if (event.which == 9) { event.preventDefault(); - that.insert_code_cell_after(); - } + var cell = that.selected_cell(); + if (cell instanceof CodeCell) { + var ta = cell.element.find("textarea.input_area"); + ta.val(ta.val() + " "); + }; + }; }); }; @@ -112,6 +195,19 @@ Notebook.prototype.selected_index = function () { }; +Notebook.prototype.cell_for_msg = function (msg_id) { + var cell_id = this.msg_cell_map[msg_id]; + var result = null; + this.cell_elements().filter(function (index) { + cell = $(this).data("cell"); + if (cell.cell_id === cell_id) { + result = cell; + }; + }); + return result; +}; + + Notebook.prototype.selected_cell = function () { return this.cell_elements().eq(this.selected_index()).data("cell"); } @@ -301,13 +397,63 @@ Notebook.prototype.code_to_text = function (index) { Notebook.prototype.collapse = function (index) { var i = this.index_or_selected(index); this.cells()[i].collapse(); -} +}; Notebook.prototype.expand = function (index) { var i = this.index_or_selected(index); this.cells()[i].expand(); -} +}; + + +// Kernel related things + +Notebook.prototype.start_kernel = function () { + this.kernel = new Kernel("kernel" + this.next_kernel_number); + this.next_kernel_number = this.next_kernel_number + 1; + this.kernel.start_kernel(this._kernel_started, this); +}; + + +Notebook.prototype._kernel_started = function () { + console.log("Kernel started: ", this.kernel.kernel_id); + this.kernel.start_session(this._session_started, this); +}; + + +Notebook.prototype._session_started = function () { + console.log("Session started: ", this.kernel.session_id); + var that = this; + + this.kernel.shell_channel.onmessage = function (e) { + reply = $.parseJSON(e.data); + console.log(reply); + var msg_type = reply.msg_type; + var cell = that.cell_for_msg(reply.parent_header.msg_id); + if (msg_type === "execute_reply") { + cell.set_prompt(reply.content.execution_count); + }; + }; + + this.kernel.iopub_channel.onmessage = function (e) { + reply = $.parseJSON(e.data); + console.log(reply); + var msg_type = reply.msg_type; + var cell = that.cell_for_msg(reply.parent_header.msg_id); + if (msg_type === "stream") { + cell.expand(); + cell.append_stream(reply.content.data + "\n"); + } else if (msg_type === "pyout" || msg_type === "display_data") { + cell.expand(); + cell.append_display_data(reply.content.data); + }; + }; +}; + + +Notebook.prototype._handle_execute_reply = function (reply, cell) { + cell.set_prompt(reply.content.execution_count); +}; //============================================================================ @@ -324,6 +470,7 @@ var Cell = function (notebook) { this.element.data("cell", this); this.bind_events(); } + this.cell_id = uuid(); }; @@ -394,9 +541,41 @@ CodeCell.prototype.create_element = function () { ).append( $('
').addClass('output_area') ); - output.hide(); cell.append(input).append(output); this.element = cell; + this.collapse() +}; + + +CodeCell.prototype.append_stream = function (data) { + var data_list = data.split("\n"); + console.log(data_list); + if (data_list.length > 0) { + for (var i=0; i").append(toinsert)); + }; + } +}; + + +CodeCell.prototype.append_display_data = function (data) { + if (data["image/svg+xml"] !== undefined) { + this.append_svg(data["image/svg+xml"]); + } else if (data["text/plain"] !== undefined) { + console.log(data["text/plain"]); + this.append_stream(data["text/plain"]); + }; +}; + +CodeCell.prototype.append_svg = function (svg) { + this.element.find("div.output_area").append(svg); +}; + + +CodeCell.prototype.clear_output = function () { + this.element.find("div.output_area").html(""); }; @@ -429,6 +608,10 @@ CodeCell.prototype.set_output_prompt = function (number) { }; +CodeCell.prototype.get_code = function () { + return this.element.find("textarea.input_area").val(); +}; + //============================================================================ // TextCell //============================================================================ @@ -508,34 +691,71 @@ TextCell.prototype.config_mathjax = function () { //============================================================================ -var KernelManager = function () { - this.kernelid = null; - this.baseurl = "/kernels"; +var Kernel = function (kernel_id) { + this.kernel_id = kernel_id; + this.base_url = "/kernels"; + this.kernel_url = this.base_url + "/" + this.kernel_id + this.session_id = null; +}; + + +Kernel.prototype.get_msg = function (msg_type, content) { + var msg = { + header : { + msg_id : uuid(), + username : "bgranger", + session: this.session_id + }, + msg_type : msg_type, + content : content, + parent_header : {} + }; + return msg; +} + +Kernel.prototype.start_kernel = function (callback, context) { + $.post(this.kernel_url, function () { + callback.call(context); + }); }; -KernelManager.prototype.create_kernel = function () { +Kernel.prototype.start_session = function (callback, context) { var that = this; - $.post(this.baseurl, function (data) { - that.kernelid = data; - }, 'json'); + $.post(this.kernel_url + "/sessions", + function (session_id) { + that._handle_start_session(session_id, callback, context); + }, + 'json'); } -KernelManager.prototype.execute = function (code, callback) { - var msg = { - header : {msg_id : 0, username : "bgranger", session: 0}, - msg_type : "execute_request", - content : {code : code} +Kernel.prototype._handle_start_session = function (session_id, callback, context) { + this.session_id = session_id; + this.session_url = this.kernel_url + "/sessions/" + this.session_id; + this._start_channels(); + callback.call(context); +}; + + +Kernel.prototype._start_channels = function () { + var ws_url = "ws://127.0.0.1:8888" + this.session_url; + this.shell_channel = new WebSocket(ws_url + "/shell"); + this.iopub_channel = new WebSocket(ws_url + "/iopub"); +} + + +Kernel.prototype.execute = function (code) { + var content = { + code : code, + silent : false, + user_variables : [], + user_expressions : {} }; - var settings = { - data : JSON.stringify(msg), - processData : false, - contentType : "application/json", - success : callback, - type : "POST" - } - var url = this.baseurl + "/" + this.kernelid + "/" + "" + var msg = this.get_msg("execute_request", content); + + this.shell_channel.send(JSON.stringify(msg)); + return msg.header.msg_id; } @@ -579,4 +799,8 @@ $(document).ready(function () { $("#sort").buttonset(); $("#sort_cells").click(function () {IPYTHON.notebook.sort_cells();}); + $("#toggle").buttonset(); + $("#collapse").click(function () {IPYTHON.notebook.collapse();}); + $("#expand").click(function () {IPYTHON.notebook.expand();}); + }); \ No newline at end of file diff --git a/IPython/frontend/html/notebook/templates/notebook.html b/IPython/frontend/html/notebook/templates/notebook.html index 8241dd7..17a56e0 100644 --- a/IPython/frontend/html/notebook/templates/notebook.html +++ b/IPython/frontend/html/notebook/templates/notebook.html @@ -67,6 +67,10 @@ + + + +
diff --git a/IPython/zmq/ipkernel.py b/IPython/zmq/ipkernel.py index ceea88d..fec751d 100755 --- a/IPython/zmq/ipkernel.py +++ b/IPython/zmq/ipkernel.py @@ -46,7 +46,6 @@ from session import Session, Message from zmqshell import ZMQInteractiveShell - #----------------------------------------------------------------------------- # Main kernel class #-----------------------------------------------------------------------------