##// END OF EJS Templates
Merge pull request #3294 from Carreau/jsfix...
Matthias Bussonnier -
r10616:98765425 merge
parent child Browse files
Show More
@@ -1,481 +1,484 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Kernel
10 10 //============================================================================
11 11
12 12 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule Kernel
16 16 */
17 17
18 18 var IPython = (function (IPython) {
19 19
20 20 var utils = IPython.utils;
21 21
22 22 // Initialization and connection.
23 23 /**
24 24 * A Kernel Class to communicate with the Python kernel
25 25 * @Class Kernel
26 26 */
27 27 var Kernel = function (base_url) {
28 28 this.kernel_id = null;
29 29 this.shell_channel = null;
30 30 this.iopub_channel = null;
31 31 this.stdin_channel = null;
32 32 this.base_url = base_url;
33 33 this.running = false;
34 34 this.username = "username";
35 35 this.session_id = utils.uuid();
36 36 this._msg_callbacks = {};
37 37
38 38 if (typeof(WebSocket) !== 'undefined') {
39 39 this.WebSocket = WebSocket;
40 40 } else if (typeof(MozWebSocket) !== 'undefined') {
41 41 this.WebSocket = MozWebSocket;
42 42 } else {
43 43 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 44 };
45 45 };
46 46
47 47
48 48 Kernel.prototype._get_msg = function (msg_type, content) {
49 49 var msg = {
50 50 header : {
51 51 msg_id : utils.uuid(),
52 52 username : this.username,
53 53 session : this.session_id,
54 54 msg_type : msg_type
55 55 },
56 56 metadata : {},
57 57 content : content,
58 58 parent_header : {}
59 59 };
60 60 return msg;
61 61 };
62 62
63 63 /**
64 64 * Start the Python kernel
65 65 * @method start
66 66 */
67 67 Kernel.prototype.start = function (notebook_id) {
68 68 var that = this;
69 69 if (!this.running) {
70 70 var qs = $.param({notebook:notebook_id});
71 71 var url = this.base_url + '?' + qs;
72 72 $.post(url,
73 73 $.proxy(that._kernel_started,that),
74 74 'json'
75 75 );
76 76 };
77 77 };
78 78
79 79 /**
80 80 * Restart the python kernel.
81 81 *
82 82 * Emit a 'status_restarting.Kernel' event with
83 83 * the current object as parameter
84 84 *
85 85 * @method restart
86 86 */
87 87 Kernel.prototype.restart = function () {
88 88 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
89 89 var that = this;
90 90 if (this.running) {
91 91 this.stop_channels();
92 92 var url = this.kernel_url + "/restart";
93 93 $.post(url,
94 94 $.proxy(that._kernel_started, that),
95 95 'json'
96 96 );
97 97 };
98 98 };
99 99
100 100
101 101 Kernel.prototype._kernel_started = function (json) {
102 102 console.log("Kernel started: ", json.kernel_id);
103 103 this.running = true;
104 104 this.kernel_id = json.kernel_id;
105 105 this.ws_url = json.ws_url;
106 106 this.kernel_url = this.base_url + "/" + this.kernel_id;
107 107 this.start_channels();
108 108 $([IPython.events]).trigger('status_started.Kernel', {kernel: this});
109 109 };
110 110
111 111
112 112 Kernel.prototype._websocket_closed = function(ws_url, early) {
113 113 this.stop_channels();
114 114 $([IPython.events]).trigger('websocket_closed.Kernel',
115 115 {ws_url: ws_url, kernel: this, early: early}
116 116 );
117 117 };
118 118
119 119 /**
120 120 * Start the `shell`and `iopub` channels.
121 121 * Will stop and restart them if they already exist.
122 122 *
123 123 * @method start_channels
124 124 */
125 125 Kernel.prototype.start_channels = function () {
126 126 var that = this;
127 127 this.stop_channels();
128 128 var ws_url = this.ws_url + this.kernel_url;
129 129 console.log("Starting WebSockets:", ws_url);
130 130 this.shell_channel = new this.WebSocket(ws_url + "/shell");
131 131 this.stdin_channel = new this.WebSocket(ws_url + "/stdin");
132 132 this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
133 133 send_cookie = function(){
134 134 // send the session id so the Session object Python-side
135 135 // has the same identity
136 136 this.send(that.session_id + ':' + document.cookie);
137 137 };
138 138 var already_called_onclose = false; // only alert once
139 139 var ws_closed_early = function(evt){
140 140 if (already_called_onclose){
141 141 return;
142 142 }
143 143 already_called_onclose = true;
144 144 if ( ! evt.wasClean ){
145 145 that._websocket_closed(ws_url, true);
146 146 }
147 147 };
148 148 var ws_closed_late = function(evt){
149 149 if (already_called_onclose){
150 150 return;
151 151 }
152 152 already_called_onclose = true;
153 153 if ( ! evt.wasClean ){
154 154 that._websocket_closed(ws_url, false);
155 155 }
156 156 };
157 157 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
158 158 for (var i=0; i < channels.length; i++) {
159 159 channels[i].onopen = send_cookie;
160 160 channels[i].onclose = ws_closed_early;
161 161 }
162 162 // switch from early-close to late-close message after 1s
163 163 setTimeout(function() {
164 164 for (var i=0; i < channels.length; i++) {
165 165 if (channels[i] !== null) {
166 166 channels[i].onclose = ws_closed_late;
167 167 }
168 168 }
169 169 }, 1000);
170 170 this.shell_channel.onmessage = $.proxy(this._handle_shell_reply, this);
171 171 this.iopub_channel.onmessage = $.proxy(this._handle_iopub_reply, this);
172 172 this.stdin_channel.onmessage = $.proxy(this._handle_input_request, this);
173 173
174 174 $([IPython.events]).on('send_input_reply.Kernel', function(evt, data) {
175 175 that.send_input_reply(data);
176 176 });
177 177 };
178 178
179 179 /**
180 180 * Start the `shell`and `iopub` channels.
181 181 * @method stop_channels
182 182 */
183 183 Kernel.prototype.stop_channels = function () {
184 184 var channels = [this.shell_channel, this.iopub_channel, this.stdin_channel];
185 185 for (var i=0; i < channels.length; i++) {
186 186 if ( channels[i] !== null ) {
187 187 channels[i].onclose = function (evt) {};
188 188 channels[i].close();
189 189 }
190 190 };
191 191 this.shell_channel = this.iopub_channel = this.stdin_channel = null;
192 192 };
193 193
194 194 // Main public methods.
195 195
196 196 /**
197 197 * Get info on object asynchronoulsy
198 198 *
199 199 * @async
200 200 * @param objname {string}
201 201 * @param callback {dict}
202 202 * @method object_info_request
203 203 *
204 204 * @example
205 205 *
206 206 * When calling this method pass a callbacks structure of the form:
207 207 *
208 208 * callbacks = {
209 209 * 'object_info_reply': object_info_reply_callback
210 210 * }
211 211 *
212 212 * The `object_info_reply_callback` will be passed the content object of the
213 213 *
214 214 * `object_into_reply` message documented in
215 215 * [IPython dev documentation](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
216 216 */
217 217 Kernel.prototype.object_info_request = function (objname, callbacks) {
218 218 if(typeof(objname)!=null && objname!=null)
219 219 {
220 220 var content = {
221 221 oname : objname.toString(),
222 222 };
223 223 var msg = this._get_msg("object_info_request", content);
224 224 this.shell_channel.send(JSON.stringify(msg));
225 225 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
226 226 return msg.header.msg_id;
227 227 }
228 228 return;
229 229 }
230 230
231 231 /**
232 232 * Execute given code into kernel, and pass result to callback.
233 233 *
234 * TODO: document input_request in callbacks
235 *
234 236 * @async
235 237 * @method execute
236 238 * @param {string} code
237 * @param callback {Object} With the following keys
238 * @param callback.'execute_reply' {function}
239 * @param callback.'output' {function}
240 * @param callback.'clear_output' {function}
241 * @param callback.'set_next_input' {function}
239 * @param [callbacks] {Object} With the optional following keys
240 * @param callbacks.'execute_reply' {function}
241 * @param callbacks.'output' {function}
242 * @param callbacks.'clear_output' {function}
243 * @param callbacks.'set_next_input' {function}
242 244 * @param {object} [options]
243 245 * @param [options.silent=false] {Boolean}
244 246 * @param [options.user_expressions=empty_dict] {Dict}
245 247 * @param [options.user_variables=empty_list] {List od Strings}
246 248 * @param [options.allow_stdin=false] {Boolean} true|false
247 249 *
248 250 * @example
249 251 *
250 252 * The options object should contain the options for the execute call. Its default
251 253 * values are:
252 254 *
253 255 * options = {
254 256 * silent : true,
255 257 * user_variables : [],
256 258 * user_expressions : {},
257 259 * allow_stdin : false
258 260 * }
259 261 *
260 262 * When calling this method pass a callbacks structure of the form:
261 263 *
262 264 * callbacks = {
263 265 * 'execute_reply': execute_reply_callback,
264 266 * 'output': output_callback,
265 267 * 'clear_output': clear_output_callback,
266 268 * 'set_next_input': set_next_input_callback
267 269 * }
268 270 *
269 271 * The `execute_reply_callback` will be passed the content and metadata
270 272 * objects of the `execute_reply` message documented
271 273 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#execute)
272 274 *
273 275 * The `output_callback` will be passed `msg_type` ('stream','display_data','pyout','pyerr')
274 276 * of the output and the content and metadata objects of the PUB/SUB channel that contains the
275 277 * output:
276 278 *
277 279 * http://ipython.org/ipython-doc/dev/development/messaging.html#messages-on-the-pub-sub-socket
278 280 *
279 281 * The `clear_output_callback` will be passed a content object that contains
280 282 * stdout, stderr and other fields that are booleans, as well as the metadata object.
281 283 *
282 284 * The `set_next_input_callback` will be passed the text that should become the next
283 285 * input cell.
284 286 */
285 287 Kernel.prototype.execute = function (code, callbacks, options) {
286 288
287 289 var content = {
288 290 code : code,
289 291 silent : true,
290 292 user_variables : [],
291 293 user_expressions : {},
292 294 allow_stdin : false
293 295 };
296 callbacks = callbacks || {};
294 297 if (callbacks.input_request !== undefined) {
295 298 content.allow_stdin = true;
296 299 }
297 300 $.extend(true, content, options)
298 301 $([IPython.events]).trigger('execution_request.Kernel', {kernel: this, content:content});
299 302 var msg = this._get_msg("execute_request", content);
300 303 this.shell_channel.send(JSON.stringify(msg));
301 304 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
302 305 return msg.header.msg_id;
303 306 };
304 307
305 308 /**
306 309 * When calling this method pass a callbacks structure of the form:
307 310 *
308 311 * callbacks = {
309 312 * 'complete_reply': complete_reply_callback
310 313 * }
311 314 *
312 315 * The `complete_reply_callback` will be passed the content object of the
313 316 * `complete_reply` message documented
314 317 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
315 318 *
316 319 * @method complete
317 320 * @param line {integer}
318 321 * @param cursor_pos {integer}
319 322 * @param {dict} callbacks
320 323 * @param callbacks.complete_reply {function} `complete_reply_callback`
321 324 *
322 325 */
323 326 Kernel.prototype.complete = function (line, cursor_pos, callbacks) {
324 327 callbacks = callbacks || {};
325 328 var content = {
326 329 text : '',
327 330 line : line,
328 331 cursor_pos : cursor_pos
329 332 };
330 333 var msg = this._get_msg("complete_request", content);
331 334 this.shell_channel.send(JSON.stringify(msg));
332 335 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
333 336 return msg.header.msg_id;
334 337 };
335 338
336 339
337 340 Kernel.prototype.interrupt = function () {
338 341 if (this.running) {
339 342 $([IPython.events]).trigger('status_interrupting.Kernel', {kernel: this});
340 343 $.post(this.kernel_url + "/interrupt");
341 344 };
342 345 };
343 346
344 347
345 348 Kernel.prototype.kill = function () {
346 349 if (this.running) {
347 350 this.running = false;
348 351 var settings = {
349 352 cache : false,
350 353 type : "DELETE"
351 354 };
352 355 $.ajax(this.kernel_url, settings);
353 356 };
354 357 };
355 358
356 359 Kernel.prototype.send_input_reply = function (input) {
357 360 var content = {
358 361 value : input,
359 362 };
360 363 $([IPython.events]).trigger('input_reply.Kernel', {kernel: this, content:content});
361 364 var msg = this._get_msg("input_reply", content);
362 365 this.stdin_channel.send(JSON.stringify(msg));
363 366 return msg.header.msg_id;
364 367 };
365 368
366 369
367 370 // Reply handlers
368 371
369 372 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
370 373 var callbacks = this._msg_callbacks[msg_id];
371 374 return callbacks;
372 375 };
373 376
374 377
375 378 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
376 379 this._msg_callbacks[msg_id] = callbacks || {};
377 380 }
378 381
379 382
380 383 Kernel.prototype._handle_shell_reply = function (e) {
381 384 var reply = $.parseJSON(e.data);
382 385 $([IPython.events]).trigger('shell_reply.Kernel', {kernel: this, reply:reply});
383 386 var header = reply.header;
384 387 var content = reply.content;
385 388 var metadata = reply.metadata;
386 389 var msg_type = header.msg_type;
387 390 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
388 391 if (callbacks !== undefined) {
389 392 var cb = callbacks[msg_type];
390 393 if (cb !== undefined) {
391 394 cb(content, metadata);
392 395 }
393 396 };
394 397
395 398 if (content.payload !== undefined) {
396 399 var payload = content.payload || [];
397 400 this._handle_payload(callbacks, payload);
398 401 }
399 402 };
400 403
401 404
402 405 Kernel.prototype._handle_payload = function (callbacks, payload) {
403 406 var l = payload.length;
404 407 // Payloads are handled by triggering events because we don't want the Kernel
405 408 // to depend on the Notebook or Pager classes.
406 409 for (var i=0; i<l; i++) {
407 410 if (payload[i].source === 'IPython.kernel.zmq.page.page') {
408 411 var data = {'text':payload[i].text}
409 412 $([IPython.events]).trigger('open_with_text.Pager', data);
410 413 } else if (payload[i].source === 'IPython.kernel.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
411 414 if (callbacks.set_next_input !== undefined) {
412 415 callbacks.set_next_input(payload[i].text)
413 416 }
414 417 }
415 418 };
416 419 };
417 420
418 421
419 422 Kernel.prototype._handle_iopub_reply = function (e) {
420 423 var reply = $.parseJSON(e.data);
421 424 var content = reply.content;
422 425 var msg_type = reply.header.msg_type;
423 426 var metadata = reply.metadata;
424 427 var callbacks = this.get_callbacks_for_msg(reply.parent_header.msg_id);
425 428 if (msg_type !== 'status' && callbacks === undefined) {
426 429 // Message not from one of this notebook's cells and there are no
427 430 // callbacks to handle it.
428 431 return;
429 432 }
430 433 var output_types = ['stream','display_data','pyout','pyerr'];
431 434 if (output_types.indexOf(msg_type) >= 0) {
432 435 var cb = callbacks['output'];
433 436 if (cb !== undefined) {
434 437 cb(msg_type, content, metadata);
435 438 }
436 439 } else if (msg_type === 'status') {
437 440 if (content.execution_state === 'busy') {
438 441 $([IPython.events]).trigger('status_busy.Kernel', {kernel: this});
439 442 } else if (content.execution_state === 'idle') {
440 443 $([IPython.events]).trigger('status_idle.Kernel', {kernel: this});
441 444 } else if (content.execution_state === 'restarting') {
442 445 $([IPython.events]).trigger('status_restarting.Kernel', {kernel: this});
443 446 } else if (content.execution_state === 'dead') {
444 447 this.stop_channels();
445 448 $([IPython.events]).trigger('status_dead.Kernel', {kernel: this});
446 449 };
447 450 } else if (msg_type === 'clear_output') {
448 451 var cb = callbacks['clear_output'];
449 452 if (cb !== undefined) {
450 453 cb(content, metadata);
451 454 }
452 455 };
453 456 };
454 457
455 458
456 459 Kernel.prototype._handle_input_request = function (e) {
457 460 var request = $.parseJSON(e.data);
458 461 var header = request.header;
459 462 var content = request.content;
460 463 var metadata = request.metadata;
461 464 var msg_type = header.msg_type;
462 465 if (msg_type !== 'input_request') {
463 466 console.log("Invalid input request!", request);
464 467 return;
465 468 }
466 469 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
467 470 if (callbacks !== undefined) {
468 471 var cb = callbacks[msg_type];
469 472 if (cb !== undefined) {
470 473 cb(content, metadata);
471 474 }
472 475 };
473 476 };
474 477
475 478
476 479 IPython.Kernel = Kernel;
477 480
478 481 return IPython;
479 482
480 483 }(IPython));
481 484
General Comments 0
You need to be logged in to leave comments. Login now