##// END OF EJS Templates
Merge pull request #4195 from minrk/widget-msg...
Min RK -
r13319:de09adf2 merge
parent child Browse files
Show More
@@ -0,0 +1,198 b''
1 //----------------------------------------------------------------------------
2 // Copyright (C) 2013 The IPython Development Team
3 //
4 // Distributed under the terms of the BSD License. The full license is in
5 // the file COPYING, distributed as part of this software.
6 //----------------------------------------------------------------------------
7
8 //============================================================================
9 // Comm and CommManager bases
10 //============================================================================
11 /**
12 * Base Comm classes
13 * @module IPython
14 * @namespace IPython
15 * @submodule comm
16 */
17
18 var IPython = (function (IPython) {
19 "use strict";
20
21 //-----------------------------------------------------------------------
22 // CommManager class
23 //-----------------------------------------------------------------------
24
25 var CommManager = function (kernel) {
26 this.comms = {};
27 this.targets = {};
28 if (kernel !== undefined) {
29 this.init_kernel(kernel);
30 }
31 };
32
33 CommManager.prototype.init_kernel = function (kernel) {
34 // connect the kernel, and register message handlers
35 this.kernel = kernel;
36 var msg_types = ['comm_open', 'comm_msg', 'comm_close'];
37 for (var i = 0; i < msg_types.length; i++) {
38 var msg_type = msg_types[i];
39 kernel.register_iopub_handler(msg_type, $.proxy(this[msg_type], this));
40 }
41 };
42
43 CommManager.prototype.new_comm = function (target_name, data, callbacks, metadata) {
44 // Create a new Comm, register it, and open its Kernel-side counterpart
45 // Mimics the auto-registration in `Comm.__init__` in the IPython Comm
46 var comm = new Comm(target_name);
47 this.register_comm(comm);
48 comm.open(data, callbacks, metadata);
49 return comm;
50 };
51
52 CommManager.prototype.register_target = function (target_name, f) {
53 // Register a target function for a given target name
54 this.targets[target_name] = f;
55 };
56
57 CommManager.prototype.unregister_target = function (target_name, f) {
58 // Unregister a target function for a given target name
59 delete this.targets[target_name];
60 };
61
62 CommManager.prototype.register_comm = function (comm) {
63 // Register a comm in the mapping
64 this.comms[comm.comm_id] = comm;
65 comm.kernel = this.kernel;
66 return comm.comm_id;
67 };
68
69 CommManager.prototype.unregister_comm = function (comm_id) {
70 // Remove a comm from the mapping
71 delete this.comms[comm_id];
72 };
73
74 // comm message handlers
75
76 CommManager.prototype.comm_open = function (msg) {
77 var content = msg.content;
78 var f = this.targets[content.target_name];
79 if (f === undefined) {
80 console.log("No such target registered: ", content.target_name);
81 console.log("Available targets are: ", this.targets);
82 return;
83 }
84 var comm = new Comm(content.target_name, content.comm_id);
85 this.register_comm(comm);
86 try {
87 f(comm, msg);
88 } catch (e) {
89 console.log("Exception opening new comm:", e, msg);
90 comm.close();
91 this.unregister_comm(comm);
92 }
93 };
94
95 CommManager.prototype.comm_close = function (msg) {
96 var content = msg.content;
97 var comm = this.comms[content.comm_id];
98 if (comm === undefined) {
99 return;
100 }
101 delete this.comms[content.comm_id];
102 try {
103 comm.handle_close(msg);
104 } catch (e) {
105 console.log("Exception closing comm: ", e, msg);
106 }
107 };
108
109 CommManager.prototype.comm_msg = function (msg) {
110 var content = msg.content;
111 var comm = this.comms[content.comm_id];
112 if (comm === undefined) {
113 return;
114 }
115 try {
116 comm.handle_msg(msg);
117 } catch (e) {
118 console.log("Exception handling comm msg: ", e, msg);
119 }
120 };
121
122 //-----------------------------------------------------------------------
123 // Comm base class
124 //-----------------------------------------------------------------------
125
126 var Comm = function (target_name, comm_id) {
127 this.target_name = target_name;
128 this.comm_id = comm_id || IPython.utils.uuid();
129 this._msg_callback = this._close_callback = null;
130 };
131
132 // methods for sending messages
133 Comm.prototype.open = function (data, callbacks, metadata) {
134 var content = {
135 comm_id : this.comm_id,
136 target_name : this.target_name,
137 data : data || {},
138 };
139 return this.kernel.send_shell_message("comm_open", content, callbacks, metadata);
140 };
141
142 Comm.prototype.send = function (data, callbacks, metadata) {
143 var content = {
144 comm_id : this.comm_id,
145 data : data || {},
146 };
147 return this.kernel.send_shell_message("comm_msg", content, callbacks, metadata);
148 };
149
150 Comm.prototype.close = function (data, callbacks, metadata) {
151 var content = {
152 comm_id : this.comm_id,
153 data : data || {},
154 };
155 return this.kernel.send_shell_message("comm_close", content, callbacks, metadata);
156 };
157
158 // methods for registering callbacks for incoming messages
159 Comm.prototype._register_callback = function (key, callback) {
160 this['_' + key + '_callback'] = callback;
161 };
162
163 Comm.prototype.on_msg = function (callback) {
164 this._register_callback('msg', callback);
165 };
166
167 Comm.prototype.on_close = function (callback) {
168 this._register_callback('close', callback);
169 };
170
171 // methods for handling incoming messages
172
173 Comm.prototype._maybe_callback = function (key, msg) {
174 var callback = this['_' + key + '_callback'];
175 if (callback) {
176 try {
177 callback(msg);
178 } catch (e) {
179 console.log("Exception in Comm callback", e, msg);
180 }
181 }
182 };
183
184 Comm.prototype.handle_msg = function (msg) {
185 this._maybe_callback('msg', msg);
186 };
187
188 Comm.prototype.handle_close = function (msg) {
189 this._maybe_callback('close', msg);
190 };
191
192 IPython.CommManager = CommManager;
193 IPython.Comm = Comm;
194
195 return IPython;
196
197 }(IPython));
198
@@ -0,0 +1,2 b''
1 from .manager import *
2 from .comm import *
@@ -0,0 +1,140 b''
1 """Base class for a Comm"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 import uuid
15
16 from IPython.config import LoggingConfigurable
17 from IPython.core.getipython import get_ipython
18
19 from IPython.utils.traitlets import Instance, Unicode, Bytes, Bool, Dict, Any
20
21 #-----------------------------------------------------------------------------
22 # Code
23 #-----------------------------------------------------------------------------
24
25 class Comm(LoggingConfigurable):
26
27 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
28 def _shell_default(self):
29 return get_ipython()
30
31 iopub_socket = Any()
32 def _iopub_socket_default(self):
33 return self.shell.kernel.iopub_socket
34 session = Instance('IPython.kernel.zmq.session.Session')
35 def _session_default(self):
36 if self.shell is None:
37 return
38 return self.shell.kernel.session
39
40 target_name = Unicode('comm')
41
42 topic = Bytes()
43 def _topic_default(self):
44 return ('comm-%s' % self.comm_id).encode('ascii')
45
46 _open_data = Dict(help="data dict, if any, to be included in comm_open")
47 _close_data = Dict(help="data dict, if any, to be included in comm_close")
48
49 _msg_callback = Any()
50 _close_callback = Any()
51
52 _closed = Bool(False)
53 comm_id = Unicode()
54 def _comm_id_default(self):
55 return uuid.uuid4().hex
56
57 primary = Bool(True, help="Am I the primary or secondary Comm?")
58
59 def __init__(self, target_name='', data=None, **kwargs):
60 if target_name:
61 kwargs['target_name'] = target_name
62 super(Comm, self).__init__(**kwargs)
63 get_ipython().comm_manager.register_comm(self)
64 if self.primary:
65 # I am primary, open my peer.
66 self.open(data)
67
68 def _publish_msg(self, msg_type, data=None, metadata=None, **keys):
69 """Helper for sending a comm message on IOPub"""
70 data = {} if data is None else data
71 metadata = {} if metadata is None else metadata
72 self.session.send(self.iopub_socket, msg_type,
73 dict(data=data, comm_id=self.comm_id, **keys),
74 metadata=metadata,
75 parent=self.shell.get_parent(),
76 ident=self.topic,
77 )
78
79 def __del__(self):
80 """trigger close on gc"""
81 self.close()
82
83 # publishing messages
84
85 def open(self, data=None, metadata=None):
86 """Open the frontend-side version of this comm"""
87 if data is None:
88 data = self._open_data
89 self._publish_msg('comm_open', data, metadata, target_name=self.target_name)
90
91 def close(self, data=None, metadata=None):
92 """Close the frontend-side version of this comm"""
93 if self._closed:
94 # only close once
95 return
96 if data is None:
97 data = self._close_data
98 self._publish_msg('comm_close', data, metadata)
99 self._closed = True
100
101 def send(self, data=None, metadata=None):
102 """Send a message to the frontend-side version of this comm"""
103 self._publish_msg('comm_msg', data, metadata)
104
105 # registering callbacks
106
107 def on_close(self, callback):
108 """Register a callback for comm_close
109
110 Will be called with the `data` of the close message.
111
112 Call `on_close(None)` to disable an existing callback.
113 """
114 self._close_callback = callback
115
116 def on_msg(self, callback):
117 """Register a callback for comm_msg
118
119 Will be called with the `data` of any comm_msg messages.
120
121 Call `on_msg(None)` to disable an existing callback.
122 """
123 self._msg_callback = callback
124
125 # handling of incoming messages
126
127 def handle_close(self, msg):
128 """Handle a comm_close message"""
129 self.log.debug("handle_close[%s](%s)", self.comm_id, msg)
130 if self._close_callback:
131 self._close_callback(msg)
132
133 def handle_msg(self, msg):
134 """Handle a comm_msg message"""
135 self.log.debug("handle_msg[%s](%s)", self.comm_id, msg)
136 if self._msg_callback:
137 self._msg_callback(msg)
138
139
140 __all__ = ['Comm']
@@ -0,0 +1,185 b''
1 """Base class to manage comms"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 import sys
15
16 from IPython.config import LoggingConfigurable
17 from IPython.core.prompts import LazyEvaluate
18 from IPython.core.getipython import get_ipython
19
20 from IPython.utils.importstring import import_item
21 from IPython.utils.traitlets import Instance, Unicode, Dict, Any
22
23 from .comm import Comm
24
25 #-----------------------------------------------------------------------------
26 # Code
27 #-----------------------------------------------------------------------------
28
29 def lazy_keys(dikt):
30 """Return lazy-evaluated string representation of a dictionary's keys
31
32 Key list is only constructed if it will actually be used.
33 Used for debug-logging.
34 """
35 return LazyEvaluate(lambda d: list(d.keys()))
36
37
38 def with_output(method):
39 """method decorator for ensuring output is handled properly in a message handler
40
41 - sets parent header before entering the method
42 - publishes busy/idle
43 - flushes stdout/stderr after
44 """
45 def method_with_output(self, stream, ident, msg):
46 parent = msg['header']
47 self.shell.set_parent(parent)
48 self.shell.kernel._publish_status('busy', parent)
49 try:
50 return method(self, stream, ident, msg)
51 finally:
52 sys.stdout.flush()
53 sys.stderr.flush()
54 self.shell.kernel._publish_status('idle', parent)
55
56 return method_with_output
57
58
59 class CommManager(LoggingConfigurable):
60 """Manager for Comms in the Kernel"""
61
62 shell = Instance('IPython.core.interactiveshell.InteractiveShellABC')
63 def _shell_default(self):
64 return get_ipython()
65 iopub_socket = Any()
66 def _iopub_socket_default(self):
67 return self.shell.kernel.iopub_socket
68 session = Instance('IPython.kernel.zmq.session.Session')
69 def _session_default(self):
70 if self.shell is None:
71 return
72 return self.shell.kernel.session
73
74 comms = Dict()
75 targets = Dict()
76
77 # Public APIs
78
79 def register_target(self, target_name, f):
80 """Register a callable f for a given target name
81
82 f will be called with two arguments when a comm_open message is received with `target`:
83
84 - the Comm instance
85 - the `comm_open` message itself.
86
87 f can be a Python callable or an import string for one.
88 """
89 if isinstance(f, basestring):
90 f = import_item(f)
91
92 self.targets[target_name] = f
93
94 def unregister_target(self, target_name, f):
95 """Unregister a callable registered with register_target"""
96 return self.targets.pop(target_name);
97
98 def register_comm(self, comm):
99 """Register a new comm"""
100 comm_id = comm.comm_id
101 comm.shell = self.shell
102 comm.iopub_socket = self.iopub_socket
103 self.comms[comm_id] = comm
104 return comm_id
105
106 def unregister_comm(self, comm_id):
107 """Unregister a comm, and close its counterpart"""
108 # unlike get_comm, this should raise a KeyError
109 comm = self.comms.pop(comm_id)
110 comm.close()
111
112 def get_comm(self, comm_id):
113 """Get a comm with a particular id
114
115 Returns the comm if found, otherwise None.
116
117 This will not raise an error,
118 it will log messages if the comm cannot be found.
119 """
120 if comm_id not in self.comms:
121 self.log.error("No such comm: %s", comm_id)
122 self.log.debug("Current comms: %s", lazy_keys(self.comms))
123 return
124 # call, because we store weakrefs
125 comm = self.comms[comm_id]
126 return comm
127
128 # Message handlers
129 @with_output
130 def comm_open(self, stream, ident, msg):
131 """Handler for comm_open messages"""
132 content = msg['content']
133 comm_id = content['comm_id']
134 target_name = content['target_name']
135 f = self.targets.get(target_name, None)
136 comm = Comm(comm_id=comm_id,
137 shell=self.shell,
138 iopub_socket=self.iopub_socket,
139 primary=False,
140 )
141 if f is None:
142 self.log.error("No such comm target registered: %s", target_name)
143 comm.close()
144 return
145 self.register_comm(comm)
146 try:
147 f(comm, msg)
148 except Exception:
149 self.log.error("Exception opening comm with target: %s", target_name, exc_info=True)
150 comm.close()
151 self.unregister_comm(comm_id)
152
153 @with_output
154 def comm_msg(self, stream, ident, msg):
155 """Handler for comm_msg messages"""
156 content = msg['content']
157 comm_id = content['comm_id']
158 comm = self.get_comm(comm_id)
159 if comm is None:
160 # no such comm
161 return
162 try:
163 comm.handle_msg(msg)
164 except Exception:
165 self.log.error("Exception in comm_msg for %s", comm_id, exc_info=True)
166
167 @with_output
168 def comm_close(self, stream, ident, msg):
169 """Handler for comm_close messages"""
170 content = msg['content']
171 comm_id = content['comm_id']
172 comm = self.get_comm(comm_id)
173 if comm is None:
174 # no such comm
175 self.log.debug("No such comm to close: %s", comm_id)
176 return
177 del self.comms[comm_id]
178
179 try:
180 comm.handle_close(msg)
181 except Exception:
182 self.log.error("Exception handling comm_close for %s", comm_id, exc_info=True)
183
184
185 __all__ = ['CommManager']
@@ -0,0 +1,269 b''
1 {
2 "metadata": {
3 "name": ""
4 },
5 "nbformat": 3,
6 "nbformat_minor": 0,
7 "worksheets": [
8 {
9 "cells": [
10 {
11 "cell_type": "heading",
12 "level": 1,
13 "metadata": {},
14 "source": [
15 "Basic Output"
16 ]
17 },
18 {
19 "cell_type": "code",
20 "collapsed": false,
21 "input": [
22 "from IPython.display import display"
23 ],
24 "language": "python",
25 "metadata": {},
26 "outputs": []
27 },
28 {
29 "cell_type": "code",
30 "collapsed": false,
31 "input": [
32 "print 'hi'"
33 ],
34 "language": "python",
35 "metadata": {},
36 "outputs": []
37 },
38 {
39 "cell_type": "code",
40 "collapsed": false,
41 "input": [
42 "display('hi')"
43 ],
44 "language": "python",
45 "metadata": {},
46 "outputs": []
47 },
48 {
49 "cell_type": "code",
50 "collapsed": false,
51 "input": [
52 "1"
53 ],
54 "language": "python",
55 "metadata": {},
56 "outputs": []
57 },
58 {
59 "cell_type": "code",
60 "collapsed": false,
61 "input": [
62 "%matplotlib inline\n",
63 "import matplotlib.pyplot as plt\n",
64 "plt.plot([1,3,2])"
65 ],
66 "language": "python",
67 "metadata": {},
68 "outputs": []
69 },
70 {
71 "cell_type": "code",
72 "collapsed": false,
73 "input": [
74 "%%javascript\n",
75 "console.log(\"I ran!\");"
76 ],
77 "language": "python",
78 "metadata": {},
79 "outputs": []
80 },
81 {
82 "cell_type": "code",
83 "collapsed": false,
84 "input": [
85 "%%html\n",
86 "<b>bold</b>"
87 ],
88 "language": "python",
89 "metadata": {},
90 "outputs": []
91 },
92 {
93 "cell_type": "code",
94 "collapsed": false,
95 "input": [
96 "%%latex\n",
97 "$$\n",
98 "a = 5\n",
99 "$$"
100 ],
101 "language": "python",
102 "metadata": {},
103 "outputs": []
104 },
105 {
106 "cell_type": "heading",
107 "level": 1,
108 "metadata": {},
109 "source": [
110 "input_request"
111 ]
112 },
113 {
114 "cell_type": "code",
115 "collapsed": false,
116 "input": [
117 "raw_input(\"prompt > \")"
118 ],
119 "language": "python",
120 "metadata": {},
121 "outputs": []
122 },
123 {
124 "cell_type": "heading",
125 "level": 1,
126 "metadata": {},
127 "source": [
128 "set_next_input"
129 ]
130 },
131 {
132 "cell_type": "code",
133 "collapsed": false,
134 "input": [
135 "%%writefile tst.py\n",
136 "def foo():\n",
137 " pass\n"
138 ],
139 "language": "python",
140 "metadata": {},
141 "outputs": []
142 },
143 {
144 "cell_type": "code",
145 "collapsed": false,
146 "input": [
147 "%load tst.py"
148 ],
149 "language": "python",
150 "metadata": {},
151 "outputs": []
152 },
153 {
154 "cell_type": "heading",
155 "level": 1,
156 "metadata": {},
157 "source": [
158 "Pager in execute_reply"
159 ]
160 },
161 {
162 "cell_type": "code",
163 "collapsed": false,
164 "input": [
165 "plt?"
166 ],
167 "language": "python",
168 "metadata": {},
169 "outputs": []
170 },
171 {
172 "cell_type": "heading",
173 "level": 1,
174 "metadata": {},
175 "source": [
176 "object_info"
177 ]
178 },
179 {
180 "cell_type": "code",
181 "collapsed": false,
182 "input": [
183 "# press tab after parentheses\n",
184 "int("
185 ],
186 "language": "python",
187 "metadata": {},
188 "outputs": []
189 },
190 {
191 "cell_type": "heading",
192 "level": 1,
193 "metadata": {},
194 "source": [
195 "complete"
196 ]
197 },
198 {
199 "cell_type": "code",
200 "collapsed": false,
201 "input": [
202 "# pres tab after f\n",
203 "f"
204 ],
205 "language": "python",
206 "metadata": {},
207 "outputs": []
208 },
209 {
210 "cell_type": "heading",
211 "level": 1,
212 "metadata": {},
213 "source": [
214 "clear_output"
215 ]
216 },
217 {
218 "cell_type": "code",
219 "collapsed": false,
220 "input": [
221 "import sys\n",
222 "from IPython.display import clear_output"
223 ],
224 "language": "python",
225 "metadata": {},
226 "outputs": []
227 },
228 {
229 "cell_type": "code",
230 "collapsed": false,
231 "input": [
232 "for i in range(10):\n",
233 " clear_output()\n",
234 " time.sleep(0.25)\n",
235 " print i\n",
236 " sys.stdout.flush()\n",
237 " time.sleep(0.25)\n"
238 ],
239 "language": "python",
240 "metadata": {},
241 "outputs": []
242 },
243 {
244 "cell_type": "code",
245 "collapsed": false,
246 "input": [
247 "for i in range(10):\n",
248 " clear_output(wait=True)\n",
249 " time.sleep(0.25)\n",
250 " print i\n",
251 " sys.stdout.flush()\n"
252 ],
253 "language": "python",
254 "metadata": {},
255 "outputs": []
256 },
257 {
258 "cell_type": "code",
259 "collapsed": false,
260 "input": [],
261 "language": "python",
262 "metadata": {},
263 "outputs": []
264 }
265 ],
266 "metadata": {}
267 }
268 ]
269 } No newline at end of file
@@ -507,6 +507,7 b' class InteractiveShell(SingletonConfigurable):'
507 507 self.init_pdb()
508 508 self.init_extension_manager()
509 509 self.init_payload()
510 self.init_comms()
510 511 self.hooks.late_startup_hook()
511 512 atexit.register(self.atexit_operations)
512 513
@@ -2317,7 +2318,15 b' class InteractiveShell(SingletonConfigurable):'
2317 2318 def init_payload(self):
2318 2319 self.payload_manager = PayloadManager(parent=self)
2319 2320 self.configurables.append(self.payload_manager)
2320
2321
2322 #-------------------------------------------------------------------------
2323 # Things related to widgets
2324 #-------------------------------------------------------------------------
2325
2326 def init_comms(self):
2327 # not implemented in the base class
2328 pass
2329
2321 2330 #-------------------------------------------------------------------------
2322 2331 # Things related to the prefilter
2323 2332 #-------------------------------------------------------------------------
@@ -390,6 +390,18 b' IPython.utils = (function (IPython) {'
390 390 test.remove();
391 391 return Math.floor(points*pixel_per_point);
392 392 };
393
394 var always_new = function (constructor) {
395 // wrapper around contructor to avoid requiring `var a = new constructor()`
396 // useful for passing constructors as callbacks,
397 // not for programmer laziness.
398 // from http://programmers.stackexchange.com/questions/118798
399 return function () {
400 var obj = Object.create(constructor.prototype);
401 constructor.apply(obj, arguments);
402 return obj;
403 };
404 };
393 405
394 406
395 407 var url_path_join = function () {
@@ -447,6 +459,7 b' IPython.utils = (function (IPython) {'
447 459 points_to_pixels : points_to_pixels,
448 460 url_path_join : url_path_join,
449 461 splitext : splitext,
462 always_new : always_new,
450 463 browser : browser
451 464 };
452 465
@@ -17,7 +17,7 b''
17 17
18 18
19 19 /* local util for codemirror */
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}
20 var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;};
21 21
22 22 /**
23 23 *
@@ -27,16 +27,16 b' var posEq = function(a, b) {return a.line == b.line && a.ch == b.ch;}'
27 27 */
28 28 CodeMirror.commands.delSpaceToPrevTabStop = function(cm){
29 29 var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);
30 if (!posEq(from, to)) {cm.replaceRange("", from, to); return}
30 if (!posEq(from, to)) { cm.replaceRange("", from, to); return; }
31 31 var cur = cm.getCursor(), line = cm.getLine(cur.line);
32 32 var tabsize = cm.getOption('tabSize');
33 33 var chToPrevTabStop = cur.ch-(Math.ceil(cur.ch/tabsize)-1)*tabsize;
34 var from = {ch:cur.ch-chToPrevTabStop,line:cur.line}
35 var select = cm.getRange(from,cur)
36 if( select.match(/^\ +$/) != null){
37 cm.replaceRange("",from,cur)
34 from = {ch:cur.ch-chToPrevTabStop,line:cur.line};
35 var select = cm.getRange(from,cur);
36 if( select.match(/^\ +$/) !== null){
37 cm.replaceRange("",from,cur);
38 38 } else {
39 cm.deleteH(-1,"char")
39 cm.deleteH(-1,"char");
40 40 }
41 41 };
42 42
@@ -104,7 +104,7 b' var IPython = (function (IPython) {'
104 104 * @method auto_highlight
105 105 */
106 106 CodeCell.prototype.auto_highlight = function () {
107 this._auto_highlight(IPython.config.cell_magic_highlight)
107 this._auto_highlight(IPython.config.cell_magic_highlight);
108 108 };
109 109
110 110 /** @method create_element */
@@ -117,7 +117,7 b' var IPython = (function (IPython) {'
117 117 this.celltoolbar = new IPython.CellToolbar(this);
118 118
119 119 var input = $('<div></div>').addClass('input');
120 var vbox = $('<div/>').addClass('vbox box-flex1')
120 var vbox = $('<div/>').addClass('vbox box-flex1');
121 121 input.append($('<div/>').addClass('prompt input_prompt'));
122 122 vbox.append(this.celltoolbar.element);
123 123 var input_area = $('<div/>').addClass('input_area');
@@ -152,7 +152,7 b' var IPython = (function (IPython) {'
152 152 // they are sent, and remove tooltip if any, except for tab again
153 153 if (event.type === 'keydown' && event.which != key.TAB ) {
154 154 IPython.tooltip.remove_and_cancel_tooltip();
155 };
155 }
156 156
157 157 var cur = editor.getCursor();
158 158 if (event.keyCode === key.ENTER){
@@ -177,7 +177,7 b' var IPython = (function (IPython) {'
177 177 return false;
178 178 } else {
179 179 return true;
180 };
180 }
181 181 } else if (event.which === key.ESC) {
182 182 return IPython.tooltip.remove_and_cancel_tooltip(true);
183 183 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
@@ -188,7 +188,7 b' var IPython = (function (IPython) {'
188 188 return false;
189 189 } else {
190 190 return true;
191 };
191 }
192 192 } else if (event.keyCode === key.TAB && event.type == 'keydown' && event.shiftKey) {
193 193 if (editor.somethingSelected()){
194 194 var anchor = editor.getCursor("anchor");
@@ -203,7 +203,7 b' var IPython = (function (IPython) {'
203 203 } else if (event.keyCode === key.TAB && event.type == 'keydown') {
204 204 // Tab completion.
205 205 //Do not trim here because of tooltip
206 if (editor.somethingSelected()){return false}
206 if (editor.somethingSelected()) { return false; }
207 207 var pre_cursor = editor.getRange({line:cur.line,ch:0},cur);
208 208 if (pre_cursor.trim() === "") {
209 209 // Don't autocomplete if the part of the line before the cursor
@@ -219,12 +219,12 b' var IPython = (function (IPython) {'
219 219 event.stop();
220 220 this.completer.startCompletion();
221 221 return true;
222 };
222 }
223 223 } else {
224 224 // keypress/keyup also trigger on TAB press, and we don't want to
225 225 // use those to disable tab completion.
226 226 return false;
227 };
227 }
228 228 return false;
229 229 };
230 230
@@ -233,7 +233,7 b' var IPython = (function (IPython) {'
233 233
234 234 CodeCell.prototype.set_kernel = function (kernel) {
235 235 this.kernel = kernel;
236 }
236 };
237 237
238 238 /**
239 239 * Execute current code cell to the kernel
@@ -243,42 +243,65 b' var IPython = (function (IPython) {'
243 243 this.output_area.clear_output();
244 244 this.set_input_prompt('*');
245 245 this.element.addClass("running");
246 var callbacks = {
247 'execute_reply': $.proxy(this._handle_execute_reply, this),
248 'output': $.proxy(this.output_area.handle_output, this.output_area),
249 'clear_output': $.proxy(this.output_area.handle_clear_output, this.output_area),
250 'set_next_input': $.proxy(this._handle_set_next_input, this),
251 'input_request': $.proxy(this._handle_input_request, this)
252 };
246 if (this.last_msg_id) {
247 this.kernel.clear_callbacks_for_msg(this.last_msg_id);
248 }
249 var callbacks = this.get_callbacks();
250
253 251 this.last_msg_id = this.kernel.execute(this.get_text(), callbacks, {silent: false, store_history: true});
254 252 };
253
254 /**
255 * Construct the default callbacks for
256 * @method get_callbacks
257 */
258 CodeCell.prototype.get_callbacks = function () {
259 return {
260 shell : {
261 reply : $.proxy(this._handle_execute_reply, this),
262 payload : {
263 set_next_input : $.proxy(this._handle_set_next_input, this),
264 page : $.proxy(this._open_with_pager, this)
265 }
266 },
267 iopub : {
268 output : $.proxy(this.output_area.handle_output, this.output_area),
269 clear_output : $.proxy(this.output_area.handle_clear_output, this.output_area),
270 },
271 input : $.proxy(this._handle_input_request, this)
272 };
273 };
274
275 CodeCell.prototype._open_with_pager = function (payload) {
276 $([IPython.events]).trigger('open_with_text.Pager', payload);
277 };
255 278
256 279 /**
257 280 * @method _handle_execute_reply
258 281 * @private
259 282 */
260 CodeCell.prototype._handle_execute_reply = function (content) {
261 this.set_input_prompt(content.execution_count);
283 CodeCell.prototype._handle_execute_reply = function (msg) {
284 this.set_input_prompt(msg.content.execution_count);
262 285 this.element.removeClass("running");
263 286 $([IPython.events]).trigger('set_dirty.Notebook', {value: true});
264 }
287 };
265 288
266 289 /**
267 290 * @method _handle_set_next_input
268 291 * @private
269 292 */
270 CodeCell.prototype._handle_set_next_input = function (text) {
271 var data = {'cell': this, 'text': text}
293 CodeCell.prototype._handle_set_next_input = function (payload) {
294 var data = {'cell': this, 'text': payload.text};
272 295 $([IPython.events]).trigger('set_next_input.Notebook', data);
273 }
296 };
274 297
275 298 /**
276 299 * @method _handle_input_request
277 300 * @private
278 301 */
279 CodeCell.prototype._handle_input_request = function (content) {
280 this.output_area.append_raw_input(content);
281 }
302 CodeCell.prototype._handle_input_request = function (msg) {
303 this.output_area.append_raw_input(msg);
304 };
282 305
283 306
284 307 // Basic cell manipulation.
@@ -328,21 +351,23 b' var IPython = (function (IPython) {'
328 351
329 352 CodeCell.input_prompt_classical = function (prompt_value, lines_number) {
330 353 var ns = prompt_value || "&nbsp;";
331 return 'In&nbsp;[' + ns + ']:'
354 return 'In&nbsp;[' + ns + ']:';
332 355 };
333 356
334 357 CodeCell.input_prompt_continuation = function (prompt_value, lines_number) {
335 358 var html = [CodeCell.input_prompt_classical(prompt_value, lines_number)];
336 for(var i=1; i < lines_number; i++){html.push(['...:'])};
337 return html.join('</br>')
359 for(var i=1; i < lines_number; i++) {
360 html.push(['...:']);
361 }
362 return html.join('<br/>');
338 363 };
339 364
340 365 CodeCell.input_prompt_function = CodeCell.input_prompt_classical;
341 366
342 367
343 368 CodeCell.prototype.set_input_prompt = function (number) {
344 var nline = 1
345 if( this.code_mirror != undefined) {
369 var nline = 1;
370 if (this.code_mirror !== undefined) {
346 371 nline = this.code_mirror.lineCount();
347 372 }
348 373 this.input_prompt_number = number;
@@ -407,16 +432,16 b' var IPython = (function (IPython) {'
407 432 this.set_input_prompt(data.prompt_number);
408 433 } else {
409 434 this.set_input_prompt();
410 };
435 }
411 436 this.output_area.fromJSON(data.outputs);
412 437 if (data.collapsed !== undefined) {
413 438 if (data.collapsed) {
414 439 this.collapse();
415 440 } else {
416 441 this.expand();
417 };
418 };
419 };
442 }
443 }
444 }
420 445 };
421 446
422 447
@@ -426,7 +451,7 b' var IPython = (function (IPython) {'
426 451 data.cell_type = 'code';
427 452 if (this.input_prompt_number) {
428 453 data.prompt_number = this.input_prompt_number;
429 };
454 }
430 455 var outputs = this.output_area.toJSON();
431 456 data.outputs = outputs;
432 457 data.language = 'python';
@@ -438,4 +463,4 b' var IPython = (function (IPython) {'
438 463 IPython.CodeCell = CodeCell;
439 464
440 465 return IPython;
441 }(IPython)); No newline at end of file
466 }(IPython));
@@ -150,16 +150,14 b' var IPython = (function (IPython) {'
150 150 matched_text: ""
151 151 })
152 152 } else {
153 var callbacks = {
154 'complete_reply': $.proxy(this.finish_completing, this)
155 };
156 this.cell.kernel.complete(line, cur.ch, callbacks);
153 this.cell.kernel.complete(line, cur.ch, $.proxy(this.finish_completing, this));
157 154 }
158 155 };
159 156
160 Completer.prototype.finish_completing = function (content) {
157 Completer.prototype.finish_completing = function (msg) {
161 158 // let's build a function that wrap all that stuff into what is needed
162 159 // for the new completer:
160 var content = msg.content;
163 161 var matched_text = content.matched_text;
164 162 var matches = content.matches;
165 163
@@ -96,7 +96,7 b' function (marked) {'
96 96 // only do this once
97 97 $([IPython.events]).off('notebook_loaded.Notebook', first_load);
98 98 };
99
99
100 100 $([IPython.events]).on('notebook_loaded.Notebook', first_load);
101 101 $([IPython.events]).trigger('app_initialized.NotebookApp');
102 102 IPython.notebook.load_notebook(notebookName, notebookPath);
@@ -231,9 +231,10 b' var IPython = (function (IPython) {'
231 231 };
232 232
233 233
234 OutputArea.prototype.handle_output = function (msg_type, content) {
234 OutputArea.prototype.handle_output = function (msg) {
235 235 var json = {};
236 json.output_type = msg_type;
236 var msg_type = json.output_type = msg.header.msg_type;
237 var content = msg.content;
237 238 if (msg_type === "stream") {
238 239 json.text = content.data;
239 240 json.stream = content.name;
@@ -564,9 +565,10 b' var IPython = (function (IPython) {'
564 565 element.append(toinsert);
565 566 };
566 567
567 OutputArea.prototype.append_raw_input = function (content) {
568 OutputArea.prototype.append_raw_input = function (msg) {
568 569 var that = this;
569 570 this.expand();
571 var content = msg.content;
570 572 var area = this.create_output_area();
571 573
572 574 // disable any other raw_inputs, if they are left around
@@ -618,8 +620,8 b' var IPython = (function (IPython) {'
618 620 }
619 621
620 622
621 OutputArea.prototype.handle_clear_output = function (content) {
622 this.clear_output(content.wait);
623 OutputArea.prototype.handle_clear_output = function (msg) {
624 this.clear_output(msg.content.wait);
623 625 };
624 626
625 627
@@ -218,16 +218,15 b' var IPython = (function (IPython) {'
218 218 // remove everything after last open bracket
219 219 line = line.replace(endBracket, "");
220 220 return Tooltip.last_token_re.exec(line)
221 }
222
221 };
223 222
224 223 Tooltip.prototype._request_tooltip = function (cell, line) {
225 var callbacks = {
226 'object_info_reply': $.proxy(this._show, this)
227 }
224 var callbacks = { shell : {
225 reply : $.proxy(this._show, this)
226 }};
228 227 var oir_token = this.extract_oir_token(line);
229 228 var msg_id = cell.kernel.object_info_request(oir_token, callbacks);
230 }
229 };
231 230
232 231 // make an imediate completion request
233 232 Tooltip.prototype.request = function (cell, hide_if_no_docstring) {
@@ -301,7 +300,8 b' var IPython = (function (IPython) {'
301 300 Tooltip.prototype._show = function (reply) {
302 301 // move the bubble if it is not hidden
303 302 // otherwise fade it
304 this.name = reply.name;
303 var content = reply.content;
304 this.name = content.name;
305 305
306 306 // do some math to have the tooltip arrow on more or less on left or right
307 307 // width of the editor
@@ -334,20 +334,20 b' var IPython = (function (IPython) {'
334 334 });
335 335
336 336 // build docstring
337 var defstring = reply.call_def;
337 var defstring = content.call_def;
338 338 if (defstring == null) {
339 defstring = reply.init_definition;
339 defstring = content.init_definition;
340 340 }
341 341 if (defstring == null) {
342 defstring = reply.definition;
342 defstring = content.definition;
343 343 }
344 344
345 var docstring = reply.call_docstring;
345 var docstring = content.call_docstring;
346 346 if (docstring == null) {
347 docstring = reply.init_docstring;
347 docstring = content.init_docstring;
348 348 }
349 349 if (docstring == null) {
350 docstring = reply.docstring;
350 docstring = content.docstring;
351 351 }
352 352
353 353 if (docstring == null) {
@@ -16,7 +16,8 b''
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19
19 "use strict";
20
20 21 var utils = IPython.utils;
21 22
22 23 // Initialization and connection.
@@ -41,12 +42,15 b' var IPython = (function (IPython) {'
41 42 this.WebSocket = MozWebSocket;
42 43 } else {
43 44 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.');
44 };
45 }
46
45 47 this.bind_events();
48 this.init_iopub_handlers();
49 this.comm_manager = new IPython.CommManager(this);
46 50 };
47 51
48 52
49 Kernel.prototype._get_msg = function (msg_type, content) {
53 Kernel.prototype._get_msg = function (msg_type, content, metadata) {
50 54 var msg = {
51 55 header : {
52 56 msg_id : utils.uuid(),
@@ -54,19 +58,32 b' var IPython = (function (IPython) {'
54 58 session : this.session_id,
55 59 msg_type : msg_type
56 60 },
57 metadata : {},
61 metadata : metadata || {},
58 62 content : content,
59 63 parent_header : {}
60 64 };
61 65 return msg;
62 66 };
63 67
64 Kernel.prototype.bind_events = function() {
68 Kernel.prototype.bind_events = function () {
65 69 var that = this;
66 70 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
67 71 that.send_input_reply(data);
68 72 });
69 }
73 };
74
75 // Initialize the iopub handlers
76
77 Kernel.prototype.init_iopub_handlers = function () {
78 var output_types = ['stream', 'display_data', 'pyout', 'pyerr'];
79 this._iopub_handlers = {};
80 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
81 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
82
83 for (var i=0; i < output_types.length; i++) {
84 this.register_iopub_handler(output_types[i], $.proxy(this._handle_output_message, this));
85 }
86 };
70 87
71 88 /**
72 89 * Start the Python kernel
@@ -81,7 +98,7 b' var IPython = (function (IPython) {'
81 98 $.proxy(this._kernel_started, this),
82 99 'json'
83 100 );
84 };
101 }
85 102 };
86 103
87 104 /**
@@ -101,7 +118,7 b' var IPython = (function (IPython) {'
101 118 $.proxy(this._kernel_started, this),
102 119 'json'
103 120 );
104 };
121 }
105 122 };
106 123
107 124
@@ -110,11 +127,11 b' var IPython = (function (IPython) {'
110 127 this.running = true;
111 128 this.kernel_id = json.id;
112 129 var ws_url = json.ws_url;
113 if (ws_url.match(/wss?:\/\//) == null) {
130 if (ws_url.match(/wss?:\/\//) === null) {
114 131 // trailing 's' in https will become wss for secure web sockets
115 prot = location.protocol.replace('http', 'ws') + "//";
132 var prot = location.protocol.replace('http', 'ws') + "//";
116 133 ws_url = prot + location.host + ws_url;
117 };
134 }
118 135 this.ws_url = ws_url;
119 136 this.kernel_url = utils.url_path_join(this.base_url, this.kernel_id);
120 137 this.start_channels();
@@ -176,7 +193,7 b' var IPython = (function (IPython) {'
176 193 }
177 194 }, 1000);
178 195 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
179 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
196 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_message, this);
180 197 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
181 198 };
182 199
@@ -208,64 +225,62 b' var IPython = (function (IPython) {'
208 225 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
209 226 for (var i=0; i < channels.length; i++) {
210 227 if ( channels[i] !== null ) {
211 channels[i].onclose = function (evt) {};
228 channels[i].onclose = null;
212 229 channels[i].close();
213 230 }
214 };
231 }
215 232 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
216 233 };
217 234
218 235 // Main public methods.
236
237 // send a message on the Kernel's shell channel
238 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata) {
239 var msg = this._get_msg(msg_type, content, metadata);
240 this.shell_channel.send(JSON.stringify(msg));
241 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
242 return msg.header.msg_id;
243 };
219 244
220 245 /**
221 * Get info on object asynchronoulsy
246 * Get info on an object
222 247 *
223 * @async
224 248 * @param objname {string}
225 * @param callback {dict}
226 * @method object_info_request
227 *
228 * @example
249 * @param callback {function}
250 * @method object_info
229 251 *
230 * When calling this method pass a callbacks structure of the form:
231 *
232 * callbacks = {
233 * 'object_info_reply': object_info_reply_callback
234 * }
235 *
236 * The `object_info_reply_callback` will be passed the content object of the
237 *
238 * `object_into_reply` message documented in
239 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
252 * When calling this method, pass a callback function that expects one argument.
253 * The callback will be passed the complete `object_info_reply` message documented
254 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
240 255 */
241 Kernel.prototype.object_info_request = function (objname, callbacks) {
242 if(typeof(objname)!=null && objname!=null)
243 {
256 Kernel.prototype.object_info = function (objname, callback) {
257 var callbacks;
258 if (callback) {
259 callbacks = { shell : { reply : callback } };
260 }
261
262 if (typeof(objname) !== null && objname !== null) {
244 263 var content = {
245 264 oname : objname.toString(),
246 265 detail_level : 0,
247 266 };
248 var msg = this._get_msg("object_info_request", content);
249 this.shell_channel.send(JSON.stringify(msg));
250 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
251 return msg.header.msg_id;
267 return this.send_shell_message("object_info_request", content, callbacks);
252 268 }
253 269 return;
254 }
270 };
255 271
256 272 /**
257 273 * Execute given code into kernel, and pass result to callback.
258 274 *
259 * TODO: document input_request in callbacks
260 *
261 275 * @async
262 276 * @method execute
263 277 * @param {string} code
264 * @param [callbacks] {Object} With the optional following keys
265 * @param callbacks.'execute_reply' {function}
266 * @param callbacks.'output' {function}
267 * @param callbacks.'clear_output' {function}
268 * @param callbacks.'set_next_input' {function}
278 * @param [callbacks] {Object} With the following keys (all optional)
279 * @param callbacks.shell.reply {function}
280 * @param callbacks.shell.payload.[payload_name] {function}
281 * @param callbacks.iopub.output {function}
282 * @param callbacks.iopub.clear_output {function}
283 * @param callbacks.input {function}
269 284 * @param {object} [options]
270 285 * @param [options.silent=false] {Boolean}
271 286 * @param [options.user_expressions=empty_dict] {Dict}
@@ -287,27 +302,21 b' var IPython = (function (IPython) {'
287 302 * When calling this method pass a callbacks structure of the form:
288 303 *
289 304 * callbacks = {
290 * 'execute_reply': execute_reply_callback,
291 * 'output': output_callback,
292 * 'clear_output': clear_output_callback,
293 * 'set_next_input': set_next_input_callback
305 * shell : {
306 * reply : execute_reply_callback,
307 * payload : {
308 * set_next_input : set_next_input_callback,
309 * }
310 * },
311 * iopub : {
312 * output : output_callback,
313 * clear_output : clear_output_callback,
314 * },
315 * input : raw_input_callback
294 316 * }
295 317 *
296 * The `execute_reply_callback` will be passed the content and metadata
297 * objects of the `execute_reply` message documented
298 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
299 *
300 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
301 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
302 * output:
303 *
304 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
305 *
306 * The `clear_output_callback` will be passed a content object that contains
307 * stdout, stderr and other fields that are booleans, as well as the metadata object.
308 *
309 * The `set_next_input_callback` will be passed the text that should become the next
310 * input cell.
318 * Each callback will be passed the entire message as a single arugment.
319 * Payload handlers will be passed the corresponding payload and the execute_reply message.
311 320 */
312 321 Kernel.prototype.execute = function (code, callbacks, options) {
313 322
@@ -320,47 +329,39 b' var IPython = (function (IPython) {'
320 329 allow_stdin : false
321 330 };
322 331 callbacks = callbacks || {};
323 if (callbacks.input_request !== undefined) {
332 if (callbacks.input !== undefined) {
324 333 content.allow_stdin = true;
325 334 }
326 $.extend(true, content, options)
335 $.extend(true, content, options);
327 336 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
328 var msg = this._get_msg("execute_request", content);
329 this.shell_channel.send(JSON.stringify(msg));
330 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
331 return msg.header.msg_id;
337 return this.send_shell_message("execute_request", content, callbacks);
332 338 };
333 339
334 340 /**
335 * When calling this method pass a callbacks structure of the form:
341 * When calling this method, pass a function to be called with the `complete_reply` message
342 * as its only argument when it arrives.
336 343 *
337 * callbacks = {
338 * 'complete_reply': complete_reply_callback
339 * }
340 *
341 * The `complete_reply_callback` will be passed the content object of the
342 * `complete_reply` message documented
344 * `complete_reply` is documented
343 345 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
344 346 *
345 347 * @method complete
346 348 * @param line {integer}
347 349 * @param cursor_pos {integer}
348 * @param {dict} callbacks
349 * @param callbacks.complete_reply {function} `complete_reply_callback`
350 * @param callback {function}
350 351 *
351 352 */
352 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
353 callbacks = callbacks || {};
353 Kernel.prototype.complete = function (line, cursor_pos, callback) {
354 var callbacks;
355 if (callback) {
356 callbacks = { shell : { reply : callback } };
357 }
354 358 var content = {
355 359 text : '',
356 360 line : line,
357 361 block : null,
358 362 cursor_pos : cursor_pos
359 363 };
360 var msg = this._get_msg("complete_request", content);
361 this.shell_channel.send(JSON.stringify(msg));
362 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
363 return msg.header.msg_id;
364 return this.send_shell_message("complete_request", content, callbacks);
364 365 };
365 366
366 367
@@ -368,7 +369,7 b' var IPython = (function (IPython) {'
368 369 if (this.running) {
369 370 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
370 371 $.post(this.kernel_url + "/interrupt");
371 };
372 }
372 373 };
373 374
374 375
@@ -380,7 +381,7 b' var IPython = (function (IPython) {'
380 381 type : "DELETE"
381 382 };
382 383 $.ajax(this.kernel_url, settings);
383 };
384 }
384 385 };
385 386
386 387 Kernel.prototype.send_input_reply = function (input) {
@@ -396,9 +397,19 b' var IPython = (function (IPython) {'
396 397
397 398 // Reply handlers
398 399
400 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
401 this._iopub_handlers[msg_type] = callback;
402 };
403
404 Kernel.prototype.get_iopub_handler = function (msg_type) {
405 // get iopub handler for a specific message type
406 return this._iopub_handlers[msg_type];
407 };
408
409
399 410 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
400 var callbacks = this._msg_callbacks[msg_id];
401 return callbacks;
411 // get callbacks for a specific message
412 return this._msg_callbacks[msg_id];
402 413 };
403 414
404 415
@@ -407,91 +418,142 b' var IPython = (function (IPython) {'
407 418 delete this._msg_callbacks[msg_id];
408 419 }
409 420 };
410
411
421
422 /* Set callbacks for a particular message.
423 * Callbacks should be a struct of the following form:
424 * shell : {
425 *
426 * }
427
428 */
412 429 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
413 this._msg_callbacks[msg_id] = callbacks || {};
430 if (callbacks) {
431 // shallow-copy mapping, because we will modify it at the top level
432 var cbcopy = this._msg_callbacks[msg_id] = {};
433 cbcopy.shell = callbacks.shell;
434 cbcopy.iopub = callbacks.iopub;
435 cbcopy.input = callbacks.input;
436 this._msg_callbacks[msg_id] = cbcopy;
437 }
414 438 };
415 439
416 440
417 441 Kernel.prototype._handle_shell_reply = function (e) {
418 442 var reply = $.parseJSON(e.data);
419 443 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
420 var header = reply.header;
421 444 var content = reply.content;
422 445 var metadata = reply.metadata;
423 var msg_type = header.msg_type;
424 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
425 if (callbacks !== undefined) {
426 var cb = callbacks[msg_type];
427 if (cb !== undefined) {
428 cb(content, metadata);
429 }
430 };
431
432 if (content.payload !== undefined) {
433 var payload = content.payload || [];
434 this._handle_payload(callbacks, payload);
446 var parent_id = reply.parent_header.msg_id;
447 var callbacks = this.get_callbacks_for_msg(parent_id);
448 if (!callbacks || !callbacks.shell) {
449 return;
450 }
451 var shell_callbacks = callbacks.shell;
452
453 // clear callbacks on shell
454 delete callbacks.shell;
455 delete callbacks.input;
456 if (!callbacks.iopub) {
457 this.clear_callbacks_for_msg(parent_id);
458 }
459
460 if (shell_callbacks.reply !== undefined) {
461 shell_callbacks.reply(reply);
462 }
463 if (content.payload && shell_callbacks.payload) {
464 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
435 465 }
436 466 };
437 467
438 468
439 Kernel.prototype._handle_payload = function (callbacks, payload) {
440 var l = payload.length;
469 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
470 var l = payloads.length;
441 471 // Payloads are handled by triggering events because we don't want the Kernel
442 472 // to depend on the Notebook or Pager classes.
443 473 for (var i=0; i<l; i++) {
444 if (payload[i].source === 'page') {
445 var data = {'text':payload[i].text}
446 $([IPython.events]).trigger('open_with_text.Pager', data);
447 } else if (payload[i].source === 'set_next_input') {
448 if (callbacks.set_next_input !== undefined) {
449 callbacks.set_next_input(payload[i].text)
474 var payload = payloads[i];
475 var callback = payload_callbacks[payload.source];
476 if (callback) {
477 callback(payload, msg);
478 }
479 }
480 };
481
482 Kernel.prototype._handle_status_message = function (msg) {
483 var execution_state = msg.content.execution_state;
484 var parent_id = msg.parent_header.msg_id;
485
486 // dispatch status msg callbacks, if any
487 var callbacks = this.get_callbacks_for_msg(parent_id);
488 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
489 try {
490 callbacks.iopub.status(msg);
491 } catch (e) {
492 console.log("Exception in status msg handler", e);
493 }
494 }
495
496 if (execution_state === 'busy') {
497 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
498 } else if (execution_state === 'idle') {
499 // clear callbacks on idle, there can be no more
500 if (callbacks !== undefined) {
501 delete callbacks.iopub;
502 delete callbacks.input;
503 if (!callbacks.shell) {
504 this.clear_callbacks_for_msg(parent_id);
450 505 }
451 506 }
452 };
507 // trigger status_idle event
508 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
509 } else if (execution_state === 'restarting') {
510 // autorestarting is distinct from restarting,
511 // in that it means the kernel died and the server is restarting it.
512 // status_restarting sets the notification widget,
513 // autorestart shows the more prominent dialog.
514 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
515 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
516 } else if (execution_state === 'dead') {
517 this.stop_channels();
518 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
519 }
520 };
521
522
523 // handle clear_output message
524 Kernel.prototype._handle_clear_output = function (msg) {
525 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
526 if (!callbacks || !callbacks.iopub) {
527 return;
528 }
529 var callback = callbacks.iopub.clear_output;
530 if (callback) {
531 callback(msg);
532 }
453 533 };
454 534
455 535
456 Kernel.prototype._handle_iopub_reply = function (e) {
457 var reply = $.parseJSON(e.data);
458 var content = reply.content;
459 var msg_type = reply.header.msg_type;
460 var metadata = reply.metadata;
461 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
462 if (msg_type !== 'status' && callbacks === undefined) {
463 // Message not from one of this notebook's cells and there are no
464 // callbacks to handle it.
536 // handle an output message (pyout, display_data, etc.)
537 Kernel.prototype._handle_output_message = function (msg) {
538 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
539 if (!callbacks || !callbacks.iopub) {
465 540 return;
466 541 }
467 var output_types = ['stream','display_data','pyout','pyerr'];
468 if (output_types.indexOf(msg_type) >= 0) {
469 var cb = callbacks['output'];
470 if (cb !== undefined) {
471 cb(msg_type, content, metadata);
472 }
473 } else if (msg_type === 'status') {
474 if (content.execution_state === 'busy') {
475 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
476 } else if (content.execution_state === 'idle') {
477 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
478 } else if (content.execution_state === 'restarting') {
479 // autorestarting is distinct from restarting,
480 // in that it means the kernel died and the server is restarting it.
481 // status_restarting sets the notification widget,
482 // autorestart shows the more prominent dialog.
483 $([IPython.events]).trigger('status_autorestarting.Kernel', {kernel: this});
484 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
485 } else if (content.execution_state === 'dead') {
486 this.stop_channels();
487 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
488 };
489 } else if (msg_type === 'clear_output') {
490 var cb = callbacks['clear_output'];
491 if (cb !== undefined) {
492 cb(content, metadata);
493 }
494 };
542 var callback = callbacks.iopub.output;
543 if (callback) {
544 callback(msg);
545 }
546 };
547
548 // dispatch IOPub messages to respective handlers.
549 // each message type should have a handler.
550 Kernel.prototype._handle_iopub_message = function (e) {
551 var msg = $.parseJSON(e.data);
552
553 var handler = this.get_iopub_handler(msg.header.msg_type);
554 if (handler !== undefined) {
555 handler(msg);
556 }
495 557 };
496 558
497 559
@@ -506,12 +568,11 b' var IPython = (function (IPython) {'
506 568 return;
507 569 }
508 570 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
509 if (callbacks !== undefined) {
510 var cb = callbacks[msg_type];
511 if (cb !== undefined) {
512 cb(content, metadata);
571 if (callbacks) {
572 if (callbacks.input) {
573 callbacks.input(request);
513 574 }
514 };
575 }
515 576 };
516 577
517 578
@@ -232,6 +232,9 b' class="notebook_app"'
232 232 <script src="{{ static_url("base/js/events.js") }}" type="text/javascript" charset="utf-8"></script>
233 233 <script src="{{ static_url("base/js/utils.js") }}" type="text/javascript" charset="utf-8"></script>
234 234 <script src="{{ static_url("base/js/dialog.js") }}" type="text/javascript" charset="utf-8"></script>
235 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
236 <script src="{{ static_url("services/kernels/js/comm.js") }}" type="text/javascript" charset="utf-8"></script>
237 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
235 238 <script src="{{ static_url("notebook/js/layoutmanager.js") }}" type="text/javascript" charset="utf-8"></script>
236 239 <script src="{{ static_url("notebook/js/mathjaxutils.js") }}" type="text/javascript" charset="utf-8"></script>
237 240 <script src="{{ static_url("notebook/js/outputarea.js") }}" type="text/javascript" charset="utf-8"></script>
@@ -240,8 +243,6 b' class="notebook_app"'
240 243 <script src="{{ static_url("notebook/js/codecell.js") }}" type="text/javascript" charset="utf-8"></script>
241 244 <script src="{{ static_url("notebook/js/completer.js") }}" type="text/javascript" charset="utf-8"></script>
242 245 <script src="{{ static_url("notebook/js/textcell.js") }}" type="text/javascript" charset="utf-8"></script>
243 <script src="{{ static_url("services/kernels/js/kernel.js") }}" type="text/javascript" charset="utf-8"></script>
244 <script src="{{ static_url("services/sessions/js/session.js") }}" type="text/javascript" charset="utf-8"></script>
245 246 <script src="{{ static_url("notebook/js/savewidget.js") }}" type="text/javascript" charset="utf-8"></script>
246 247 <script src="{{ static_url("notebook/js/quickhelp.js") }}" type="text/javascript" charset="utf-8"></script>
247 248 <script src="{{ static_url("notebook/js/pager.js") }}" type="text/javascript" charset="utf-8"></script>
@@ -1,14 +1,5 b''
1 1 #!/usr/bin/env python
2 """A simple interactive kernel that talks to a frontend over 0MQ.
3
4 Things to do:
5
6 * Implement `set_parent` logic. Right before doing exec, the Kernel should
7 call set_parent on all the PUB objects with the message about to be executed.
8 * Implement random port and security key logic.
9 * Implement control messages.
10 * Implement event loop and poll version.
11 """
2 """An interactive kernel that talks to frontends over 0MQ."""
12 3
13 4 #-----------------------------------------------------------------------------
14 5 # Imports
@@ -145,6 +136,7 b' class Kernel(Configurable):'
145 136 profile_dir = self.profile_dir,
146 137 user_module = self.user_module,
147 138 user_ns = self.user_ns,
139 kernel = self,
148 140 )
149 141 self.shell.displayhook.session = self.session
150 142 self.shell.displayhook.pub_socket = self.iopub_socket
@@ -168,11 +160,17 b' class Kernel(Configurable):'
168 160 for msg_type in msg_types:
169 161 self.shell_handlers[msg_type] = getattr(self, msg_type)
170 162
163 comm_msg_types = [ 'comm_open', 'comm_msg', 'comm_close' ]
164 comm_manager = self.shell.comm_manager
165 for msg_type in comm_msg_types:
166 self.shell_handlers[msg_type] = getattr(comm_manager, msg_type)
167
171 168 control_msg_types = msg_types + [ 'clear_request', 'abort_request' ]
172 169 self.control_handlers = {}
173 170 for msg_type in control_msg_types:
174 171 self.control_handlers[msg_type] = getattr(self, msg_type)
175 172
173
176 174 def dispatch_control(self, msg):
177 175 """dispatch control requests"""
178 176 idents,msg = self.session.feed_identities(msg, copy=False)
@@ -367,17 +365,7 b' class Kernel(Configurable):'
367 365 __builtin__.input = input
368 366
369 367 # Set the parent message of the display hook and out streams.
370 shell.displayhook.set_parent(parent)
371 shell.display_pub.set_parent(parent)
372 shell.data_pub.set_parent(parent)
373 try:
374 sys.stdout.set_parent(parent)
375 except AttributeError:
376 pass
377 try:
378 sys.stderr.set_parent(parent)
379 except AttributeError:
380 pass
368 shell.set_parent(parent)
381 369
382 370 # Re-broadcast our input for the benefit of listening clients, and
383 371 # start computing output
@@ -583,17 +571,7 b' class Kernel(Configurable):'
583 571
584 572 # Set the parent message of the display hook and out streams.
585 573 shell = self.shell
586 shell.displayhook.set_parent(parent)
587 shell.display_pub.set_parent(parent)
588 shell.data_pub.set_parent(parent)
589 try:
590 sys.stdout.set_parent(parent)
591 except AttributeError:
592 pass
593 try:
594 sys.stderr.set_parent(parent)
595 except AttributeError:
596 pass
574 shell.set_parent(parent)
597 575
598 576 # pyin_msg = self.session.msg(u'pyin',{u'code':code}, parent=parent)
599 577 # self.iopub_socket.send(pyin_msg)
@@ -44,11 +44,12 b' from IPython.utils import openpy'
44 44 from IPython.utils.jsonutil import json_clean, encode_images
45 45 from IPython.utils.process import arg_split
46 46 from IPython.utils import py3compat
47 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes
47 from IPython.utils.traitlets import Instance, Type, Dict, CBool, CBytes, Any
48 48 from IPython.utils.warn import error
49 49 from IPython.kernel.zmq.displayhook import ZMQShellDisplayHook
50 50 from IPython.kernel.zmq.datapub import ZMQDataPublisher
51 51 from IPython.kernel.zmq.session import extract_header
52 from IPython.kernel.comm import CommManager
52 53 from session import Session
53 54
54 55 #-----------------------------------------------------------------------------
@@ -476,6 +477,8 b' class ZMQInteractiveShell(InteractiveShell):'
476 477 displayhook_class = Type(ZMQShellDisplayHook)
477 478 display_pub_class = Type(ZMQDisplayPublisher)
478 479 data_pub_class = Type(ZMQDataPublisher)
480 kernel = Any()
481 parent_header = Any()
479 482
480 483 # Override the traitlet in the parent class, because there's no point using
481 484 # readline for the kernel. Can be removed when the readline code is moved
@@ -585,6 +588,24 b' class ZMQInteractiveShell(InteractiveShell):'
585 588 )
586 589 self.payload_manager.write_payload(payload)
587 590
591 def set_parent(self, parent):
592 """Set the parent header for associating output with its triggering input"""
593 self.parent_header = parent
594 self.displayhook.set_parent(parent)
595 self.display_pub.set_parent(parent)
596 self.data_pub.set_parent(parent)
597 try:
598 sys.stdout.set_parent(parent)
599 except AttributeError:
600 pass
601 try:
602 sys.stderr.set_parent(parent)
603 except AttributeError:
604 pass
605
606 def get_parent(self):
607 return self.parent_header
608
588 609 #-------------------------------------------------------------------------
589 610 # Things related to magics
590 611 #-------------------------------------------------------------------------
@@ -593,7 +614,10 b' class ZMQInteractiveShell(InteractiveShell):'
593 614 super(ZMQInteractiveShell, self).init_magics()
594 615 self.register_magics(KernelMagics)
595 616 self.magics_manager.register_alias('ed', 'edit')
596
617
618 def init_comms(self):
619 self.comm_manager = CommManager(shell=self, parent=self)
620 self.configurables.append(self.comm_manager)
597 621
598 622
599 623 InteractiveShellABC.register(ZMQInteractiveShell)
@@ -430,7 +430,9 b" When status is 'ok', the following extra fields are present::"
430 430 # Each execution payload is a dict with string keys that may have been
431 431 # produced by the code being executed. It is retrieved by the kernel at
432 432 # the end of the execution and sent back to the front end, which can take
433 # action on it as needed. See main text for further details.
433 # action on it as needed.
434 # The only requirement of each payload dict is that it have a 'source' key,
435 # which is a string classifying the payload (e.g. 'pager').
434 436 'payload' : list(dict),
435 437
436 438 # Results for the user_variables and user_expressions.
@@ -1058,6 +1060,79 b" where the first part is the zmq.IDENTITY of the heart's DEALER on the engine, an"
1058 1060 the rest is the message sent by the monitor. No Python code ever has any
1059 1061 access to the message between the monitor's send, and the monitor's recv.
1060 1062
1063 Custom Messages
1064 ===============
1065
1066 IPython 2.0 adds a messaging system for developers to add their own objects with Frontend
1067 and Kernel-side components, and allow them to communicate with each other.
1068 To do this, IPython adds a notion of a ``Comm``, which exists on both sides,
1069 and can communicate in either direction.
1070
1071 These messages are fully symmetrical - both the Kernel and the Frontend can send each message,
1072 and no messages expect a reply.
1073 The Kernel listens for these messages on the Shell channel,
1074 and the Frontend listens for them on the IOPub channel.
1075
1076 .. versionadded:: 2.0
1077
1078 Opening a Comm
1079 --------------
1080
1081 Opening a Comm produces a ``comm_open`` message, to be sent to the other side::
1082
1083 {
1084 'comm_id' : 'u-u-i-d',
1085 'target_name' : 'my_comm',
1086 'data' : {}
1087 }
1088
1089 Every Comm has an ID and a target name.
1090 The code handling the message on the receiving side is responsible for maintaining a mapping
1091 of target_name keys to constructors.
1092 After a ``comm_open`` message has been sent,
1093 there should be a corresponding Comm instance on both sides.
1094 The ``data`` key is always a dict and can be any extra JSON information used in initialization of the comm.
1095
1096 If the ``target_name`` key is not found on the receiving side,
1097 then it should immediately reply with a ``comm_close`` message to avoid an inconsistent state.
1098
1099 Comm Messages
1100 -------------
1101
1102 Comm messages are one-way communications to update comm state,
1103 used for synchronizing widget state, or simply requesting actions of a comm's counterpart.
1104
1105 Essentially, each comm pair defines their own message specification implemented inside the ``data`` dict.
1106
1107 There are no expected replies (of course, one side can send another ``comm_msg`` in reply).
1108
1109 Message type: ``comm_msg``::
1110
1111 {
1112 'comm_id' : 'u-u-i-d',
1113 'data' : {}
1114 }
1115
1116 Tearing Down Comms
1117 ------------------
1118
1119 Since comms live on both sides, when a comm is destroyed the other side must be notified.
1120 This is done with a ``comm_close`` message.
1121
1122 Message type: ``comm_close``::
1123
1124 {
1125 'comm_id' : 'u-u-i-d',
1126 'data' : {}
1127 }
1128
1129 Output Side Effects
1130 -------------------
1131
1132 Since comm messages can execute arbitrary user code,
1133 handlers should set the parent header and publish status busy / idle,
1134 just like an execute request.
1135
1061 1136
1062 1137 ToDo
1063 1138 ====
@@ -1070,9 +1145,4 b' Missing things include:'
1070 1145 likely that with the payload concept we can build a full solution, but not
1071 1146 100% clear yet.
1072 1147
1073 * Finishing the details of the heartbeat protocol.
1074
1075 * Signal handling: specify what kind of information kernel should broadcast (or
1076 not) when it receives signals.
1077
1078 1148 .. include:: ../links.txt
General Comments 0
You need to be logged in to leave comments. Login now