##// END OF EJS Templates
Using $.proxy to clean up callbacks.
Brian E. Granger -
Show More
@@ -1,115 +1,115 b''
1 1
2 2 //============================================================================
3 3 // Kernel
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var utils = IPython.utils;
9 9
10 10 var Kernel = function () {
11 11 this.kernel_id = null;
12 12 this.base_url = "/kernels";
13 13 this.kernel_url = null;
14 14 };
15 15
16 16
17 17 Kernel.prototype.get_msg = function (msg_type, content) {
18 18 var msg = {
19 19 header : {
20 20 msg_id : utils.uuid(),
21 21 username : "bgranger",
22 22 session: this.session_id,
23 23 msg_type : msg_type
24 24 },
25 25 content : content,
26 26 parent_header : {}
27 27 };
28 28 return msg;
29 29 }
30 30
31 Kernel.prototype.start_kernel = function (callback, context) {
31 Kernel.prototype.start_kernel = function (callback) {
32 32 var that = this;
33 33 $.post(this.base_url,
34 34 function (kernel_id) {
35 that._handle_start_kernel(kernel_id, callback, context);
35 that._handle_start_kernel(kernel_id, callback);
36 36 },
37 37 'json'
38 38 );
39 39 };
40 40
41 41
42 Kernel.prototype._handle_start_kernel = function (kernel_id, callback, context) {
42 Kernel.prototype._handle_start_kernel = function (kernel_id, callback) {
43 43 this.kernel_id = kernel_id;
44 44 this.kernel_url = this.base_url + "/" + this.kernel_id;
45 45 this._start_channels();
46 callback.call(context);
46 callback();
47 47 };
48 48
49 49
50 50 Kernel.prototype._start_channels = function () {
51 51 var ws_url = "ws://127.0.0.1:8888" + this.kernel_url;
52 52 this.shell_channel = new WebSocket(ws_url + "/shell");
53 53 this.iopub_channel = new WebSocket(ws_url + "/iopub");
54 54 }
55 55
56 56
57 57 Kernel.prototype.execute = function (code) {
58 58 var content = {
59 59 code : code,
60 60 silent : false,
61 61 user_variables : [],
62 62 user_expressions : {}
63 63 };
64 64 var msg = this.get_msg("execute_request", content);
65 65 this.shell_channel.send(JSON.stringify(msg));
66 66 return msg.header.msg_id;
67 67 }
68 68
69 69
70 70 Kernel.prototype.interrupt = function () {
71 71 $.post(this.kernel_url + "/interrupt");
72 72 };
73 73
74 74
75 75 Kernel.prototype.restart = function () {
76 76 this.status_restarting();
77 77 url = this.kernel_url + "/restart"
78 78 var that = this;
79 79 $.post(url, function (kernel_id) {
80 80 console.log("Kernel restarted: " + kernel_id);
81 81 that.kernel_id = kernel_id;
82 82 that.kernel_url = that.base_url + "/" + that.kernel_id;
83 83 that.status_idle();
84 84 }, 'json');
85 85 };
86 86
87 87
88 88 Kernel.prototype.status_busy = function () {
89 89 $("#kernel_status").removeClass("status_idle");
90 90 $("#kernel_status").removeClass("status_restarting");
91 91 $("#kernel_status").addClass("status_busy");
92 92 $("#kernel_status").text("Busy");
93 93 };
94 94
95 95
96 96 Kernel.prototype.status_idle = function () {
97 97 $("#kernel_status").removeClass("status_busy");
98 98 $("#kernel_status").removeClass("status_restarting");
99 99 $("#kernel_status").addClass("status_idle");
100 100 $("#kernel_status").text("Idle");
101 101 };
102 102
103 103 Kernel.prototype.status_restarting = function () {
104 104 $("#kernel_status").removeClass("status_busy");
105 105 $("#kernel_status").removeClass("status_idle");
106 106 $("#kernel_status").addClass("status_restarting");
107 107 $("#kernel_status").text("Restarting");
108 108 };
109 109
110 110 IPython.Kernel = Kernel;
111 111
112 112 return IPython;
113 113
114 114 }(IPython));
115 115
@@ -1,515 +1,518 b''
1 1
2 2 //============================================================================
3 3 // Notebook
4 4 //============================================================================
5 5
6 6 var IPython = (function (IPython) {
7 7
8 8 var Notebook = function (selector) {
9 9 this.element = $(selector);
10 10 this.element.scroll();
11 11 this.element.data("notebook", this);
12 12 this.next_prompt_number = 1;
13 13 this.kernel = null;
14 14 this.msg_cell_map = {};
15 15 this.filename = null;
16 16 this.notebook_load_re = /%notebook load/
17 17 this.notebook_save_re = /%notebook save/
18 18 this.notebook_filename_re = /(\w)+.ipynb/
19 19 this.bind_events();
20 20 this.start_kernel();
21 21 };
22 22
23 23
24 24 Notebook.prototype.bind_events = function () {
25 25 var that = this;
26 26 $(document).keydown(function (event) {
27 27 // console.log(event);
28 28 if (event.which === 38) {
29 29 var cell = that.selected_cell();
30 30 if (cell.at_top()) {
31 31 event.preventDefault();
32 32 that.select_prev();
33 33 };
34 34 } else if (event.which === 40) {
35 35 var cell = that.selected_cell();
36 36 if (cell.at_bottom()) {
37 37 event.preventDefault();
38 38 that.select_next();
39 39 };
40 40 } else if (event.which === 13 && event.shiftKey) {
41 41 // The focus is not quite working here.
42 42 var cell = that.selected_cell();
43 43 var cell_index = that.find_cell_index(cell);
44 44 // TODO: the logic here needs to be moved into appropriate
45 45 // methods of Notebook.
46 46 if (cell instanceof IPython.CodeCell) {
47 47 event.preventDefault();
48 48 cell.clear_output();
49 49 var code = cell.get_code();
50 50 if (that.notebook_load_re.test(code)) {
51 51 var code_parts = code.split(' ');
52 52 if (code_parts.length === 3) {
53 53 that.load_notebook(code_parts[2]);
54 54 };
55 55 } else if (that.notebook_save_re.test(code)) {
56 56 var code_parts = code.split(' ');
57 57 if (code_parts.length === 3) {
58 58 that.save_notebook(code_parts[2]);
59 59 } else {
60 60 that.save_notebook()
61 61 };
62 62 } else {
63 63 var msg_id = that.kernel.execute(cell.get_code());
64 64 that.msg_cell_map[msg_id] = cell.cell_id;
65 65 };
66 66 } else if (cell instanceof IPython.TextCell) {
67 67 event.preventDefault();
68 68 cell.render();
69 69 }
70 70 if (cell_index === (that.ncells()-1)) {
71 71 that.insert_code_cell_after();
72 72 } else {
73 73 that.select(cell_index+1);
74 74 };
75 75 };
76 76 });
77 77 };
78 78
79 79
80 80 // Cell indexing, retrieval, etc.
81 81
82 82
83 83 Notebook.prototype.cell_elements = function () {
84 84 return this.element.children("div.cell");
85 85 }
86 86
87 87
88 88 Notebook.prototype.ncells = function (cell) {
89 89 return this.cell_elements().length;
90 90 }
91 91
92 92
93 93 // TODO: we are often calling cells as cells()[i], which we should optimize
94 94 // to cells(i) or a new method.
95 95 Notebook.prototype.cells = function () {
96 96 return this.cell_elements().toArray().map(function (e) {
97 97 return $(e).data("cell");
98 98 });
99 99 }
100 100
101 101
102 102 Notebook.prototype.find_cell_index = function (cell) {
103 103 var result = null;
104 104 this.cell_elements().filter(function (index) {
105 105 if ($(this).data("cell") === cell) {
106 106 result = index;
107 107 };
108 108 });
109 109 return result;
110 110 };
111 111
112 112
113 113 Notebook.prototype.index_or_selected = function (index) {
114 114 return index || this.selected_index() || 0;
115 115 }
116 116
117 117
118 118 Notebook.prototype.select = function (index) {
119 119 if (index !== undefined && index >= 0 && index < this.ncells()) {
120 120 if (this.selected_index() !== null) {
121 121 this.selected_cell().unselect();
122 122 };
123 123 this.cells()[index].select();
124 124 };
125 125 return this;
126 126 };
127 127
128 128
129 129 Notebook.prototype.select_next = function () {
130 130 var index = this.selected_index();
131 131 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
132 132 this.select(index+1);
133 133 };
134 134 return this;
135 135 };
136 136
137 137
138 138 Notebook.prototype.select_prev = function () {
139 139 var index = this.selected_index();
140 140 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
141 141 this.select(index-1);
142 142 };
143 143 return this;
144 144 };
145 145
146 146
147 147 Notebook.prototype.selected_index = function () {
148 148 var result = null;
149 149 this.cell_elements().filter(function (index) {
150 150 if ($(this).data("cell").selected === true) {
151 151 result = index;
152 152 };
153 153 });
154 154 return result;
155 155 };
156 156
157 157
158 158 Notebook.prototype.cell_for_msg = function (msg_id) {
159 159 var cell_id = this.msg_cell_map[msg_id];
160 160 var result = null;
161 161 this.cell_elements().filter(function (index) {
162 162 cell = $(this).data("cell");
163 163 if (cell.cell_id === cell_id) {
164 164 result = cell;
165 165 };
166 166 });
167 167 return result;
168 168 };
169 169
170 170
171 171 Notebook.prototype.selected_cell = function () {
172 172 return this.cell_elements().eq(this.selected_index()).data("cell");
173 173 }
174 174
175 175
176 176 // Cell insertion, deletion and moving.
177 177
178 178
179 179 Notebook.prototype.delete_cell = function (index) {
180 180 var i = index || this.selected_index();
181 181 if (i !== null && i >= 0 && i < this.ncells()) {
182 182 this.cell_elements().eq(i).remove();
183 183 if (i === (this.ncells())) {
184 184 this.select(i-1);
185 185 } else {
186 186 this.select(i);
187 187 };
188 188 };
189 189 return this;
190 190 };
191 191
192 192
193 193 Notebook.prototype.append_cell = function (cell) {
194 194 this.element.append(cell.element);
195 195 return this;
196 196 };
197 197
198 198
199 199 Notebook.prototype.insert_cell_after = function (cell, index) {
200 200 var ncells = this.ncells();
201 201 if (ncells === 0) {
202 202 this.append_cell(cell);
203 203 return this;
204 204 };
205 205 if (index >= 0 && index < ncells) {
206 206 this.cell_elements().eq(index).after(cell.element);
207 207 };
208 208 return this
209 209 };
210 210
211 211
212 212 Notebook.prototype.insert_cell_before = function (cell, index) {
213 213 var ncells = this.ncells();
214 214 if (ncells === 0) {
215 215 this.append_cell(cell);
216 216 return this;
217 217 };
218 218 if (index >= 0 && index < ncells) {
219 219 this.cell_elements().eq(index).before(cell.element);
220 220 };
221 221 return this;
222 222 };
223 223
224 224
225 225 Notebook.prototype.move_cell_up = function (index) {
226 226 var i = index || this.selected_index();
227 227 if (i !== null && i < this.ncells() && i > 0) {
228 228 var pivot = this.cell_elements().eq(i-1);
229 229 var tomove = this.cell_elements().eq(i);
230 230 if (pivot !== null && tomove !== null) {
231 231 tomove.detach();
232 232 pivot.before(tomove);
233 233 this.select(i-1);
234 234 };
235 235 };
236 236 return this;
237 237 }
238 238
239 239
240 240 Notebook.prototype.move_cell_down = function (index) {
241 241 var i = index || this.selected_index();
242 242 if (i !== null && i < (this.ncells()-1) && i >= 0) {
243 243 var pivot = this.cell_elements().eq(i+1)
244 244 var tomove = this.cell_elements().eq(i)
245 245 if (pivot !== null && tomove !== null) {
246 246 tomove.detach();
247 247 pivot.after(tomove);
248 248 this.select(i+1);
249 249 };
250 250 };
251 251 return this;
252 252 }
253 253
254 254
255 255 Notebook.prototype.sort_cells = function () {
256 256 var ncells = this.ncells();
257 257 var sindex = this.selected_index();
258 258 var swapped;
259 259 do {
260 260 swapped = false
261 261 for (var i=1; i<ncells; i++) {
262 262 current = this.cell_elements().eq(i).data("cell");
263 263 previous = this.cell_elements().eq(i-1).data("cell");
264 264 if (previous.input_prompt_number > current.input_prompt_number) {
265 265 this.move_cell_up(i);
266 266 swapped = true;
267 267 };
268 268 };
269 269 } while (swapped);
270 270 this.select(sindex);
271 271 return this;
272 272 };
273 273
274 274
275 275 Notebook.prototype.insert_code_cell_before = function (index) {
276 276 // TODO: Bounds check for i
277 277 var i = this.index_or_selected(index);
278 278 var cell = new IPython.CodeCell(this);
279 279 cell.set_input_prompt(this.next_prompt_number);
280 280 this.next_prompt_number = this.next_prompt_number + 1;
281 281 this.insert_cell_before(cell, i);
282 282 this.select(this.find_cell_index(cell));
283 283 return this;
284 284 }
285 285
286 286
287 287 Notebook.prototype.insert_code_cell_after = function (index) {
288 288 // TODO: Bounds check for i
289 289 var i = this.index_or_selected(index);
290 290 var cell = new IPython.CodeCell(this);
291 291 cell.set_input_prompt(this.next_prompt_number);
292 292 this.next_prompt_number = this.next_prompt_number + 1;
293 293 this.insert_cell_after(cell, i);
294 294 this.select(this.find_cell_index(cell));
295 295 return this;
296 296 }
297 297
298 298
299 299 Notebook.prototype.insert_text_cell_before = function (index) {
300 300 // TODO: Bounds check for i
301 301 var i = this.index_or_selected(index);
302 302 var cell = new IPython.TextCell(this);
303 303 cell.config_mathjax();
304 304 this.insert_cell_before(cell, i);
305 305 this.select(this.find_cell_index(cell));
306 306 return this;
307 307 }
308 308
309 309
310 310 Notebook.prototype.insert_text_cell_after = function (index) {
311 311 // TODO: Bounds check for i
312 312 var i = this.index_or_selected(index);
313 313 var cell = new IPython.TextCell(this);
314 314 cell.config_mathjax();
315 315 this.insert_cell_after(cell, i);
316 316 this.select(this.find_cell_index(cell));
317 317 return this;
318 318 }
319 319
320 320
321 321 Notebook.prototype.text_to_code = function (index) {
322 322 // TODO: Bounds check for i
323 323 var i = this.index_or_selected(index);
324 324 var source_element = this.cell_elements().eq(i);
325 325 var source_cell = source_element.data("cell");
326 326 if (source_cell instanceof IPython.TextCell) {
327 327 this.insert_code_cell_after(i);
328 328 var target_cell = this.cells()[i+1];
329 329 target_cell.set_code(source_cell.get_text());
330 330 source_element.remove();
331 331 };
332 332 };
333 333
334 334
335 335 Notebook.prototype.code_to_text = function (index) {
336 336 // TODO: Bounds check for i
337 337 var i = this.index_or_selected(index);
338 338 var source_element = this.cell_elements().eq(i);
339 339 var source_cell = source_element.data("cell");
340 340 if (source_cell instanceof IPython.CodeCell) {
341 341 this.insert_text_cell_after(i);
342 342 var target_cell = this.cells()[i+1];
343 343 var text = source_cell.get_code();
344 344 if (text === "") {text = target_cell.placeholder;};
345 345 target_cell.set_text(text);
346 346 source_element.remove();
347 347 target_cell.edit();
348 348 };
349 349 };
350 350
351 351
352 352 // Cell collapsing
353 353
354 354 Notebook.prototype.collapse = function (index) {
355 355 var i = this.index_or_selected(index);
356 356 this.cells()[i].collapse();
357 357 };
358 358
359 359
360 360 Notebook.prototype.expand = function (index) {
361 361 var i = this.index_or_selected(index);
362 362 this.cells()[i].expand();
363 363 };
364 364
365 365
366 366 // Kernel related things
367 367
368 368 Notebook.prototype.start_kernel = function () {
369 369 this.kernel = new IPython.Kernel();
370 this.kernel.start_kernel(this._kernel_started, this);
370 this.kernel.start_kernel($.proxy(this.kernel_started, this));
371 371 };
372 372
373 373
374 Notebook.prototype._kernel_started = function () {
375 console.log("Kernel started: ", this.kernel.kernel_id);
376 var that = this;
374 Notebook.prototype.handle_shell_reply = function (e) {
375 reply = $.parseJSON(e.data);
376 // console.log(reply);
377 var msg_type = reply.header.msg_type;
378 var cell = this.cell_for_msg(reply.parent_header.msg_id);
379 if (msg_type === "execute_reply") {
380 cell.set_input_prompt(reply.content.execution_count);
381 };
382 };
383
377 384
378 this.kernel.shell_channel.onmessage = function (e) {
379 reply = $.parseJSON(e.data);
380 // console.log(reply);
381 var msg_type = reply.header.msg_type;
382 var cell = that.cell_for_msg(reply.parent_header.msg_id);
383 if (msg_type === "execute_reply") {
384 cell.set_input_prompt(reply.content.execution_count);
385 Notebook.prototype.handle_iopub_reply = function (e) {
386 reply = $.parseJSON(e.data);
387 var content = reply.content;
388 // console.log(reply);
389 var msg_type = reply.header.msg_type;
390 var cell = this.cell_for_msg(reply.parent_header.msg_id);
391 if (msg_type === "stream") {
392 cell.expand();
393 cell.append_stream(content.data + "\n");
394 } else if (msg_type === "display_data") {
395 cell.expand();
396 cell.append_display_data(content.data);
397 } else if (msg_type === "pyout") {
398 cell.expand();
399 cell.append_pyout(content.data, content.execution_count)
400 } else if (msg_type === "pyerr") {
401 cell.expand();
402 cell.append_pyerr(content.ename, content.evalue, content.traceback);
403 } else if (msg_type === "status") {
404 if (content.execution_state === "busy") {
405 this.kernel.status_busy();
406 } else if (content.execution_state === "idle") {
407 this.kernel.status_idle();
385 408 };
386 };
409 }
410 };
387 411
388 this.kernel.iopub_channel.onmessage = function (e) {
389 reply = $.parseJSON(e.data);
390 var content = reply.content;
391 // console.log(reply);
392 var msg_type = reply.header.msg_type;
393 var cell = that.cell_for_msg(reply.parent_header.msg_id);
394 if (msg_type === "stream") {
395 cell.expand();
396 cell.append_stream(content.data + "\n");
397 } else if (msg_type === "display_data") {
398 cell.expand();
399 cell.append_display_data(content.data);
400 } else if (msg_type === "pyout") {
401 cell.expand();
402 cell.append_pyout(content.data, content.execution_count)
403 } else if (msg_type === "pyerr") {
404 cell.expand();
405 cell.append_pyerr(content.ename, content.evalue, content.traceback);
406 } else if (msg_type === "status") {
407 if (content.execution_state === "busy") {
408 that.kernel.status_busy();
409 } else if (content.execution_state === "idle") {
410 that.kernel.status_idle();
411 };
412 }
413 };
412
413 Notebook.prototype.kernel_started = function () {
414 console.log("Kernel started: ", this.kernel.kernel_id);
415 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
416 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
414 417 };
415 418
416 419
417 420 // Persistance and loading
418 421
419 422
420 423 Notebook.prototype.fromJSON = function (data) {
421 424 var ncells = this.ncells();
422 425 for (var i=0; i<ncells; i++) {
423 426 // Always delete cell 0 as they get renumbered as they are deleted.
424 427 this.delete_cell(0);
425 428 };
426 429 var new_cells = data.cells;
427 430 ncells = new_cells.length;
428 431 var cell_data = null;
429 432 for (var i=0; i<ncells; i++) {
430 433 cell_data = new_cells[i];
431 434 if (cell_data.cell_type == 'code') {
432 435 this.insert_code_cell_after();
433 436 this.selected_cell().fromJSON(cell_data);
434 437 } else if (cell_data.cell_type === 'text') {
435 438 this.insert_text_cell_after();
436 439 this.selected_cell().fromJSON(cell_data);
437 440 };
438 441 };
439 442 };
440 443
441 444
442 445 Notebook.prototype.toJSON = function () {
443 446 var cells = this.cells();
444 447 var ncells = cells.length;
445 448 cell_array = new Array(ncells);
446 449 for (var i=0; i<ncells; i++) {
447 450 cell_array[i] = cells[i].toJSON();
448 451 };
449 452 json = {
450 453 cells : cell_array
451 454 };
452 455 return json
453 456 };
454 457
455 458
456 459 Notebook.prototype.test_filename = function (filename) {
457 460 if (this.notebook_filename_re.test(filename)) {
458 461 return true;
459 462 } else {
460 463 var bad_filename = $('<div/>');
461 464 bad_filename.html(
462 465 "The filename you entered (" + filename + ") is not valid. Notebook filenames must have the following form: foo.ipynb"
463 466 );
464 467 bad_filename.dialog({title: 'Invalid filename', modal: true});
465 468 return false;
466 469 };
467 470 };
468 471
469 472 Notebook.prototype.save_notebook = function (filename) {
470 473 this.filename = filename || this.filename || '';
471 474 if (this.filename === '') {
472 475 var no_filename = $('<div/>');
473 476 no_filename.html(
474 477 "This notebook has no filename, please specify a filename of the form: foo.ipynb"
475 478 );
476 479 no_filename.dialog({title: 'Missing filename', modal: true});
477 480 return;
478 481 }
479 482 if (!this.test_filename(this.filename)) {return;}
480 483 var thedata = this.toJSON();
481 484 var settings = {
482 485 processData : false,
483 486 cache : false,
484 487 type : "PUT",
485 488 data : JSON.stringify(thedata),
486 489 success : function (data, status, xhr) {console.log(data);}
487 490 };
488 491 $.ajax("/notebooks/" + this.filename, settings);
489 492 };
490 493
491 494
492 495 Notebook.prototype.load_notebook = function (filename) {
493 496 if (!this.test_filename(filename)) {return;}
494 497 var that = this;
495 498 // We do the call with settings so we can set cache to false.
496 499 var settings = {
497 500 processData : false,
498 501 cache : false,
499 502 type : "GET",
500 503 dataType : "json",
501 504 success : function (data, status, xhr) {
502 505 that.fromJSON(data);
503 506 that.filename = filename;
504 507 that.kernel.restart();
505 508 }
506 509 };
507 510 $.ajax("/notebooks/" + filename, settings);
508 511 }
509 512
510 513 IPython.Notebook = Notebook;
511 514
512 515 return IPython;
513 516
514 517 }(IPython));
515 518
General Comments 0
You need to be logged in to leave comments. Login now