##// END OF EJS Templates
More of the initial split cell capability.
Brian Granger -
Show More
@@ -1,1133 +1,1150
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 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15
16 16 var Notebook = function (selector) {
17 17 this.read_only = IPython.read_only;
18 18 this.element = $(selector);
19 19 this.element.scroll();
20 20 this.element.data("notebook", this);
21 21 this.next_prompt_number = 1;
22 22 this.kernel = null;
23 23 this.clipboard = null;
24 24 this.dirty = false;
25 25 this.msg_cell_map = {};
26 26 this.metadata = {};
27 27 this.control_key_active = false;
28 28 this.style();
29 29 this.create_elements();
30 30 this.bind_events();
31 31 this.set_tooltipontab(true);
32 32 this.set_smartcompleter(true);
33 33 this.set_timebeforetooltip(1200);
34 34 this.set_autoindent(true);
35 35 };
36 36
37 37
38 38 Notebook.prototype.style = function () {
39 39 $('div#notebook').addClass('border-box-sizing');
40 40 };
41 41
42 42
43 43 Notebook.prototype.create_elements = function () {
44 44 // We add this end_space div to the end of the notebook div to:
45 45 // i) provide a margin between the last cell and the end of the notebook
46 46 // ii) to prevent the div from scrolling up when the last cell is being
47 47 // edited, but is too low on the page, which browsers will do automatically.
48 48 var that = this;
49 49 var end_space = $('<div class="end_space"></div>').height("30%");
50 50 end_space.dblclick(function (e) {
51 51 if (that.read_only) return;
52 52 var ncells = that.ncells();
53 53 that.insert_code_cell_below(ncells-1);
54 54 });
55 55 this.element.append(end_space);
56 56 $('div#notebook').addClass('border-box-sizing');
57 57 };
58 58
59 59
60 60 Notebook.prototype.bind_events = function () {
61 61 var that = this;
62 62 $(document).keydown(function (event) {
63 63 // console.log(event);
64 64 if (that.read_only) return true;
65 65 if (event.which === 27) {
66 66 // Intercept escape at highest level to avoid closing
67 67 // websocket connection with firefox
68 68 event.preventDefault();
69 69 }
70 70 if (event.which === 38 && !event.shiftKey) {
71 71 var cell = that.selected_cell();
72 72 if (cell.at_top()) {
73 73 event.preventDefault();
74 74 that.select_prev();
75 75 };
76 76 } else if (event.which === 40 && !event.shiftKey) {
77 77 var cell = that.selected_cell();
78 78 if (cell.at_bottom()) {
79 79 event.preventDefault();
80 80 that.select_next();
81 81 };
82 82 } else if (event.which === 13 && event.shiftKey) {
83 83 that.execute_selected_cell();
84 84 return false;
85 85 } else if (event.which === 13 && event.ctrlKey) {
86 86 that.execute_selected_cell({terminal:true});
87 87 return false;
88 88 } else if (event.which === 77 && event.ctrlKey) {
89 89 that.control_key_active = true;
90 90 return false;
91 91 } else if (event.which === 88 && that.control_key_active) {
92 92 // Cut selected cell = x
93 93 that.cut_cell();
94 94 that.control_key_active = false;
95 95 return false;
96 96 } else if (event.which === 67 && that.control_key_active) {
97 97 // Copy selected cell = c
98 98 that.copy_cell();
99 99 that.control_key_active = false;
100 100 return false;
101 101 } else if (event.which === 86 && that.control_key_active) {
102 102 // Paste selected cell = v
103 103 that.paste_cell();
104 104 that.control_key_active = false;
105 105 return false;
106 106 } else if (event.which === 68 && that.control_key_active) {
107 107 // Delete selected cell = d
108 108 that.delete_cell();
109 109 that.control_key_active = false;
110 110 return false;
111 111 } else if (event.which === 65 && that.control_key_active) {
112 112 // Insert code cell above selected = a
113 113 that.insert_code_cell_above();
114 114 that.control_key_active = false;
115 115 return false;
116 116 } else if (event.which === 66 && that.control_key_active) {
117 117 // Insert code cell below selected = b
118 118 that.insert_code_cell_below();
119 119 that.control_key_active = false;
120 120 return false;
121 121 } else if (event.which === 89 && that.control_key_active) {
122 122 // To code = y
123 123 that.to_code();
124 124 that.control_key_active = false;
125 125 return false;
126 126 } else if (event.which === 77 && that.control_key_active) {
127 127 // To markdown = m
128 128 that.to_markdown();
129 129 that.control_key_active = false;
130 130 return false;
131 131 } else if (event.which === 84 && that.control_key_active) {
132 132 // Toggle output = t
133 133 that.toggle_output();
134 134 that.control_key_active = false;
135 135 return false;
136 136 } else if (event.which === 83 && that.control_key_active) {
137 137 // Save notebook = s
138 138 IPython.save_widget.save_notebook();
139 139 that.control_key_active = false;
140 140 return false;
141 141 } else if (event.which === 74 && that.control_key_active) {
142 142 // Move cell down = j
143 143 that.move_cell_down();
144 144 that.control_key_active = false;
145 145 return false;
146 146 } else if (event.which === 75 && that.control_key_active) {
147 147 // Move cell up = k
148 148 that.move_cell_up();
149 149 that.control_key_active = false;
150 150 return false;
151 151 } else if (event.which === 80 && that.control_key_active) {
152 152 // Select previous = p
153 153 that.select_prev();
154 154 that.control_key_active = false;
155 155 return false;
156 156 } else if (event.which === 78 && that.control_key_active) {
157 157 // Select next = n
158 158 that.select_next();
159 159 that.control_key_active = false;
160 160 return false;
161 161 } else if (event.which === 76 && that.control_key_active) {
162 162 // Toggle line numbers = l
163 163 that.cell_toggle_line_numbers();
164 164 that.control_key_active = false;
165 165 return false;
166 166 } else if (event.which === 73 && that.control_key_active) {
167 167 // Interrupt kernel = i
168 168 IPython.notebook.kernel.interrupt();
169 169 that.control_key_active = false;
170 170 return false;
171 171 } else if (event.which === 190 && that.control_key_active) {
172 172 // Restart kernel = . # matches qt console
173 173 IPython.notebook.restart_kernel();
174 174 that.control_key_active = false;
175 175 return false;
176 176 } else if (event.which === 72 && that.control_key_active) {
177 177 // Show keyboard shortcuts = h
178 178 IPython.quick_help.show_keyboard_shortcuts();
179 179 that.control_key_active = false;
180 180 return false;
181 181 } else if (that.control_key_active) {
182 182 that.control_key_active = false;
183 183 return true;
184 184 };
185 185 return true;
186 186 });
187 187
188 188 this.element.bind('collapse_pager', function () {
189 189 var app_height = $('div#main_app').height(); // content height
190 190 var splitter_height = $('div#pager_splitter').outerHeight(true);
191 191 var new_height = app_height - splitter_height;
192 192 that.element.animate({height : new_height + 'px'}, 'fast');
193 193 });
194 194
195 195 this.element.bind('expand_pager', function () {
196 196 var app_height = $('div#main_app').height(); // content height
197 197 var splitter_height = $('div#pager_splitter').outerHeight(true);
198 198 var pager_height = $('div#pager').outerHeight(true);
199 199 var new_height = app_height - pager_height - splitter_height;
200 200 that.element.animate({height : new_height + 'px'}, 'fast');
201 201 });
202 202
203 203 this.element.bind('collapse_left_panel', function () {
204 204 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
205 205 var new_margin = splitter_width;
206 206 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
207 207 });
208 208
209 209 this.element.bind('expand_left_panel', function () {
210 210 var splitter_width = $('div#left_panel_splitter').outerWidth(true);
211 211 var left_panel_width = IPython.left_panel.width;
212 212 var new_margin = splitter_width + left_panel_width;
213 213 $('div#notebook_panel').animate({marginLeft : new_margin + 'px'}, 'fast');
214 214 });
215 215
216 216 $(window).bind('beforeunload', function () {
217 217 // TODO: Make killing the kernel configurable.
218 218 var kill_kernel = false;
219 219 if (kill_kernel) {
220 220 that.kernel.kill();
221 221 }
222 222 if (that.dirty && ! that.read_only) {
223 223 return "You have unsaved changes that will be lost if you leave this page.";
224 224 };
225 225 // Null is the *only* return value that will make the browser not
226 226 // pop up the "don't leave" dialog.
227 227 return null;
228 228 });
229 229 };
230 230
231 231
232 232 Notebook.prototype.scroll_to_bottom = function () {
233 233 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
234 234 };
235 235
236 236
237 237 Notebook.prototype.scroll_to_top = function () {
238 238 this.element.animate({scrollTop:0}, 0);
239 239 };
240 240
241 241
242 242 // Cell indexing, retrieval, etc.
243 243
244 244
245 245 Notebook.prototype.cell_elements = function () {
246 246 return this.element.children("div.cell");
247 247 };
248 248
249 249
250 250 Notebook.prototype.ncells = function (cell) {
251 251 return this.cell_elements().length;
252 252 };
253 253
254 254
255 255 // TODO: we are often calling cells as cells()[i], which we should optimize
256 256 // to cells(i) or a new method.
257 257 Notebook.prototype.cells = function () {
258 258 return this.cell_elements().toArray().map(function (e) {
259 259 return $(e).data("cell");
260 260 });
261 261 };
262 262
263 263
264 264 Notebook.prototype.find_cell_index = function (cell) {
265 265 var result = null;
266 266 this.cell_elements().filter(function (index) {
267 267 if ($(this).data("cell") === cell) {
268 268 result = index;
269 269 };
270 270 });
271 271 return result;
272 272 };
273 273
274 274
275 275 Notebook.prototype.index_or_selected = function (index) {
276 276 return index || this.selected_index() || 0;
277 277 };
278 278
279 279
280 280 Notebook.prototype.select = function (index) {
281 281 if (index !== undefined && index >= 0 && index < this.ncells()) {
282 282 if (this.selected_index() !== null) {
283 283 this.selected_cell().unselect();
284 284 };
285 285 this.cells()[index].select();
286 286 };
287 287 return this;
288 288 };
289 289
290 290
291 291 Notebook.prototype.select_next = function () {
292 292 var index = this.selected_index();
293 293 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
294 294 this.select(index+1);
295 295 };
296 296 return this;
297 297 };
298 298
299 299
300 300 Notebook.prototype.select_prev = function () {
301 301 var index = this.selected_index();
302 302 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
303 303 this.select(index-1);
304 304 };
305 305 return this;
306 306 };
307 307
308 308
309 309 Notebook.prototype.selected_index = function () {
310 310 var result = null;
311 311 this.cell_elements().filter(function (index) {
312 312 if ($(this).data("cell").selected === true) {
313 313 result = index;
314 314 };
315 315 });
316 316 return result;
317 317 };
318 318
319 319
320 320 Notebook.prototype.cell_for_msg = function (msg_id) {
321 321 var cell_id = this.msg_cell_map[msg_id];
322 322 var result = null;
323 323 this.cell_elements().filter(function (index) {
324 324 cell = $(this).data("cell");
325 325 if (cell.cell_id === cell_id) {
326 326 result = cell;
327 327 };
328 328 });
329 329 return result;
330 330 };
331 331
332 332
333 333 Notebook.prototype.selected_cell = function () {
334 334 return this.cell_elements().eq(this.selected_index()).data("cell");
335 335 };
336 336
337 337
338 338 // Cell insertion, deletion and moving.
339 339
340 340 Notebook.prototype.delete_cell = function (index) {
341 341 var i = index || this.selected_index();
342 342 if (i !== null && i >= 0 && i < this.ncells()) {
343 343 this.cell_elements().eq(i).remove();
344 344 if (i === (this.ncells())) {
345 345 this.select(i-1);
346 346 } else {
347 347 this.select(i);
348 348 };
349 349 };
350 350 this.dirty = true;
351 351 return this;
352 352 };
353 353
354 354
355 355 Notebook.prototype.append_cell = function (cell) {
356 356 this.element.find('div.end_space').before(cell.element);
357 357 this.dirty = true;
358 358 return this;
359 359 };
360 360
361 361
362 362 Notebook.prototype.insert_cell_below = function (cell, index) {
363 363 var ncells = this.ncells();
364 364 if (ncells === 0) {
365 365 this.append_cell(cell);
366 366 return this;
367 367 };
368 368 if (index >= 0 && index < ncells) {
369 369 this.cell_elements().eq(index).after(cell.element);
370 370 };
371 371 this.dirty = true;
372 372 return this;
373 373 };
374 374
375 375
376 376 Notebook.prototype.insert_cell_above = function (cell, index) {
377 377 var ncells = this.ncells();
378 378 if (ncells === 0) {
379 379 this.append_cell(cell);
380 380 return this;
381 381 };
382 382 if (index >= 0 && index < ncells) {
383 383 this.cell_elements().eq(index).before(cell.element);
384 384 };
385 385 this.dirty = true;
386 386 return this;
387 387 };
388 388
389 389
390 390 Notebook.prototype.move_cell_up = function (index) {
391 391 var i = index || this.selected_index();
392 392 if (i !== null && i < this.ncells() && i > 0) {
393 393 var pivot = this.cell_elements().eq(i-1);
394 394 var tomove = this.cell_elements().eq(i);
395 395 if (pivot !== null && tomove !== null) {
396 396 tomove.detach();
397 397 pivot.before(tomove);
398 398 this.select(i-1);
399 399 };
400 400 };
401 401 this.dirty = true;
402 402 return this;
403 403 };
404 404
405 405
406 406 Notebook.prototype.move_cell_down = function (index) {
407 407 var i = index || this.selected_index();
408 408 if (i !== null && i < (this.ncells()-1) && i >= 0) {
409 409 var pivot = this.cell_elements().eq(i+1);
410 410 var tomove = this.cell_elements().eq(i);
411 411 if (pivot !== null && tomove !== null) {
412 412 tomove.detach();
413 413 pivot.after(tomove);
414 414 this.select(i+1);
415 415 };
416 416 };
417 417 this.dirty = true;
418 418 return this;
419 419 };
420 420
421 421
422 422 Notebook.prototype.sort_cells = function () {
423 423 var ncells = this.ncells();
424 424 var sindex = this.selected_index();
425 425 var swapped;
426 426 do {
427 427 swapped = false;
428 428 for (var i=1; i<ncells; i++) {
429 429 current = this.cell_elements().eq(i).data("cell");
430 430 previous = this.cell_elements().eq(i-1).data("cell");
431 431 if (previous.input_prompt_number > current.input_prompt_number) {
432 432 this.move_cell_up(i);
433 433 swapped = true;
434 434 };
435 435 };
436 436 } while (swapped);
437 437 this.select(sindex);
438 438 return this;
439 439 };
440 440
441 441
442 442 Notebook.prototype.insert_code_cell_above = function (index) {
443 443 // TODO: Bounds check for i
444 444 var i = this.index_or_selected(index);
445 445 var cell = new IPython.CodeCell(this);
446 446 cell.set_input_prompt();
447 447 this.insert_cell_above(cell, i);
448 448 this.select(this.find_cell_index(cell));
449 449 return cell;
450 450 };
451 451
452 452
453 453 Notebook.prototype.insert_code_cell_below = function (index) {
454 454 // TODO: Bounds check for i
455 455 var i = this.index_or_selected(index);
456 456 var cell = new IPython.CodeCell(this);
457 457 cell.set_input_prompt();
458 458 this.insert_cell_below(cell, i);
459 459 this.select(this.find_cell_index(cell));
460 460 return cell;
461 461 };
462 462
463 463
464 464 Notebook.prototype.insert_html_cell_above = function (index) {
465 465 // TODO: Bounds check for i
466 466 var i = this.index_or_selected(index);
467 467 var cell = new IPython.HTMLCell(this);
468 468 cell.config_mathjax();
469 469 this.insert_cell_above(cell, i);
470 470 this.select(this.find_cell_index(cell));
471 471 return cell;
472 472 };
473 473
474 474
475 475 Notebook.prototype.insert_html_cell_below = function (index) {
476 476 // TODO: Bounds check for i
477 477 var i = this.index_or_selected(index);
478 478 var cell = new IPython.HTMLCell(this);
479 479 cell.config_mathjax();
480 480 this.insert_cell_below(cell, i);
481 481 this.select(this.find_cell_index(cell));
482 482 return cell;
483 483 };
484 484
485 485
486 486 Notebook.prototype.insert_markdown_cell_above = function (index) {
487 487 // TODO: Bounds check for i
488 488 var i = this.index_or_selected(index);
489 489 var cell = new IPython.MarkdownCell(this);
490 490 cell.config_mathjax();
491 491 this.insert_cell_above(cell, i);
492 492 this.select(this.find_cell_index(cell));
493 493 return cell;
494 494 };
495 495
496 496
497 497 Notebook.prototype.insert_markdown_cell_below = function (index) {
498 498 // TODO: Bounds check for i
499 499 var i = this.index_or_selected(index);
500 500 var cell = new IPython.MarkdownCell(this);
501 501 cell.config_mathjax();
502 502 this.insert_cell_below(cell, i);
503 503 this.select(this.find_cell_index(cell));
504 504 return cell;
505 505 };
506 506
507 507
508 508 Notebook.prototype.to_code = function (index) {
509 509 // TODO: Bounds check for i
510 510 var i = this.index_or_selected(index);
511 511 var source_element = this.cell_elements().eq(i);
512 512 var source_cell = source_element.data("cell");
513 513 if (source_cell instanceof IPython.HTMLCell ||
514 514 source_cell instanceof IPython.MarkdownCell) {
515 515 this.insert_code_cell_below(i);
516 516 var target_cell = this.cells()[i+1];
517 517 target_cell.set_code(source_cell.get_source());
518 518 source_element.remove();
519 519 target_cell.select();
520 520 };
521 521 this.dirty = true;
522 522 };
523 523
524 524
525 525 Notebook.prototype.to_markdown = function (index) {
526 526 // TODO: Bounds check for i
527 527 var i = this.index_or_selected(index);
528 528 var source_element = this.cell_elements().eq(i);
529 529 var source_cell = source_element.data("cell");
530 530 var target_cell = null;
531 531 if (source_cell instanceof IPython.CodeCell) {
532 532 this.insert_markdown_cell_below(i);
533 533 target_cell = this.cells()[i+1];
534 534 var text = source_cell.get_code();
535 535 } else if (source_cell instanceof IPython.HTMLCell) {
536 536 this.insert_markdown_cell_below(i);
537 537 target_cell = this.cells()[i+1];
538 538 var text = source_cell.get_source();
539 539 if (text === source_cell.placeholder) {
540 540 text = target_cell.placeholder;
541 541 }
542 542 }
543 543 if (target_cell !== null) {
544 544 if (text === "") {text = target_cell.placeholder;};
545 545 target_cell.set_source(text);
546 546 source_element.remove();
547 547 target_cell.edit();
548 548 }
549 549 this.dirty = true;
550 550 };
551 551
552 552
553 553 Notebook.prototype.to_html = function (index) {
554 554 // TODO: Bounds check for i
555 555 var i = this.index_or_selected(index);
556 556 var source_element = this.cell_elements().eq(i);
557 557 var source_cell = source_element.data("cell");
558 558 var target_cell = null;
559 559 if (source_cell instanceof IPython.CodeCell) {
560 560 this.insert_html_cell_below(i);
561 561 target_cell = this.cells()[i+1];
562 562 var text = source_cell.get_code();
563 563 } else if (source_cell instanceof IPython.MarkdownCell) {
564 564 this.insert_html_cell_below(i);
565 565 target_cell = this.cells()[i+1];
566 566 var text = source_cell.get_source();
567 567 if (text === source_cell.placeholder) {
568 568 text = target_cell.placeholder;
569 569 }
570 570 }
571 571 if (target_cell !== null) {
572 572 if (text === "") {text = target_cell.placeholder;};
573 573 target_cell.set_source(text);
574 574 source_element.remove();
575 575 target_cell.edit();
576 576 }
577 577 this.dirty = true;
578 578 };
579 579
580 580
581 // Copy/Paste
581 // Copy/Paste/Merge/Split
582 582
583 583 Notebook.prototype.enable_paste = function () {
584 584 var that = this;
585 585 $('#paste_cell').removeClass('ui-state-disabled')
586 586 .on('click', function () {that.paste_cell();});
587 587 $('#paste_cell_above').removeClass('ui-state-disabled')
588 588 .on('click', function () {that.paste_cell_above();});
589 589 $('#paste_cell_below').removeClass('ui-state-disabled')
590 590 .on('click', function () {that.paste_cell_below();});
591 591 };
592 592
593 593
594 594 Notebook.prototype.disable_paste = function () {
595 595 $('#paste_cell').addClass('ui-state-disabled').off('click');
596 596 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
597 597 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
598 598 };
599 599
600 600
601 601 Notebook.prototype.cut_cell = function () {
602 602 this.copy_cell();
603 603 this.delete_cell();
604 604 }
605 605
606 606 Notebook.prototype.copy_cell = function () {
607 607 var cell = this.selected_cell();
608 608 this.clipboard = cell.toJSON();
609 609 this.enable_paste();
610 610 };
611 611
612 612
613 613 Notebook.prototype.paste_cell = function () {
614 614 if (this.clipboard !== null) {
615 615 var cell_data = this.clipboard;
616 616 if (cell_data.cell_type == 'code') {
617 617 new_cell = this.insert_code_cell_above();
618 618 new_cell.fromJSON(cell_data);
619 619 } else if (cell_data.cell_type === 'html') {
620 620 new_cell = this.insert_html_cell_above();
621 621 new_cell.fromJSON(cell_data);
622 622 } else if (cell_data.cell_type === 'markdown') {
623 623 new_cell = this.insert_markdown_cell_above();
624 624 new_cell.fromJSON(cell_data);
625 625 };
626 626 };
627 627 this.select_next();
628 628 this.delete_cell();
629 629 };
630 630
631 631
632 632 Notebook.prototype.paste_cell_above = function () {
633 633 if (this.clipboard !== null) {
634 634 var cell_data = this.clipboard;
635 635 if (cell_data.cell_type == 'code') {
636 636 new_cell = this.insert_code_cell_above();
637 637 new_cell.fromJSON(cell_data);
638 638 } else if (cell_data.cell_type === 'html') {
639 639 new_cell = this.insert_html_cell_above();
640 640 new_cell.fromJSON(cell_data);
641 641 } else if (cell_data.cell_type === 'markdown') {
642 642 new_cell = this.insert_markdown_cell_above();
643 643 new_cell.fromJSON(cell_data);
644 644 };
645 645 };
646 646 };
647 647
648 648
649 649 Notebook.prototype.paste_cell_below = function () {
650 650 if (this.clipboard !== null) {
651 651 var cell_data = this.clipboard;
652 652 if (cell_data.cell_type == 'code') {
653 653 new_cell = this.insert_code_cell_above();
654 654 new_cell.fromJSON(cell_data);
655 655 } else if (cell_data.cell_type === 'html') {
656 656 new_cell = this.insert_html_cell_above();
657 657 new_cell.fromJSON(cell_data);
658 658 } else if (cell_data.cell_type === 'markdown') {
659 659 new_cell = this.insert_markdown_cell_above();
660 660 new_cell.fromJSON(cell_data);
661 661 };
662 662 };
663 663 };
664 664
665 665
666 Notebook.prototype.split_cell = function () {
667 var cell = this.selected_cell();
668 if (cell instanceof IPython.CodeCell) {
669 var cursor = cell.code_mirror.getCursor();
670 var last_line_num = cell.code_mirror.lineCount()-1;
671 var last_line_len = cell.code_mirror.getLine(last_line_num).length;
672 var end = {line:last_line_num, ch:last_line_len}
673 var texta = cell.code_mirror.getRange({line:0,ch:0}, cursor);
674 var textb = cell.code_mirror.getRange(cursor, end);
675 texta = texta.replace(/^\n+/, '').replace(/\n+$/, '');
676 textb = textb.replace(/^\n+/, '').replace(/\n+$/, '');
677 cell.set_code(texta);
678 var new_cell = this.insert_code_cell_below();
679 new_cell.set_code(textb);
680 };
681 };
682
666 683 // Cell collapsing and output clearing
667 684
668 685 Notebook.prototype.collapse = function (index) {
669 686 var i = this.index_or_selected(index);
670 687 this.cells()[i].collapse();
671 688 this.dirty = true;
672 689 };
673 690
674 691
675 692 Notebook.prototype.expand = function (index) {
676 693 var i = this.index_or_selected(index);
677 694 this.cells()[i].expand();
678 695 this.dirty = true;
679 696 };
680 697
681 698
682 699 Notebook.prototype.toggle_output = function (index) {
683 700 var i = this.index_or_selected(index);
684 701 this.cells()[i].toggle_output();
685 702 this.dirty = true;
686 703 };
687 704
688 705
689 706 Notebook.prototype.set_timebeforetooltip = function (time) {
690 707 this.time_before_tooltip = time;
691 708 };
692 709
693 710 Notebook.prototype.set_tooltipontab = function (state) {
694 711 this.tooltip_on_tab = state;
695 712 };
696 713
697 714 Notebook.prototype.set_smartcompleter = function (state) {
698 715 this.smart_completer = state;
699 716 };
700 717
701 718 Notebook.prototype.set_autoindent = function (state) {
702 719 var cells = this.cells();
703 720 len = cells.length;
704 721 for (var i=0; i<len; i++) {
705 722 cells[i].set_autoindent(state);
706 723 };
707 724 };
708 725
709 726
710 727 Notebook.prototype.clear_all_output = function () {
711 728 var ncells = this.ncells();
712 729 var cells = this.cells();
713 730 for (var i=0; i<ncells; i++) {
714 731 if (cells[i] instanceof IPython.CodeCell) {
715 732 cells[i].clear_output(true,true,true);
716 733 }
717 734 };
718 735 this.dirty = true;
719 736 };
720 737
721 738 // Other cell functions: line numbers, ...
722 739
723 740 Notebook.prototype.cell_toggle_line_numbers = function() {
724 741 this.selected_cell().toggle_line_numbers();
725 742 };
726 743
727 744 // Kernel related things
728 745
729 746 Notebook.prototype.start_kernel = function () {
730 747 this.kernel = new IPython.Kernel();
731 748 var notebook_id = IPython.save_widget.get_notebook_id();
732 749 this.kernel.start(notebook_id, $.proxy(this.kernel_started, this));
733 750 };
734 751
735 752
736 753 Notebook.prototype.restart_kernel = function () {
737 754 var that = this;
738 755 var notebook_id = IPython.save_widget.get_notebook_id();
739 756
740 757 var dialog = $('<div/>');
741 758 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
742 759 $(document).append(dialog);
743 760 dialog.dialog({
744 761 resizable: false,
745 762 modal: true,
746 763 title: "Restart kernel or continue running?",
747 764 closeText: '',
748 765 buttons : {
749 766 "Restart": function () {
750 767 that.kernel.restart($.proxy(that.kernel_started, that));
751 768 $(this).dialog('close');
752 769 },
753 770 "Continue running": function () {
754 771 $(this).dialog('close');
755 772 }
756 773 }
757 774 });
758 775 };
759 776
760 777
761 778 Notebook.prototype.kernel_started = function () {
762 779 console.log("Kernel started: ", this.kernel.kernel_id);
763 780 this.kernel.shell_channel.onmessage = $.proxy(this.handle_shell_reply,this);
764 781 this.kernel.iopub_channel.onmessage = $.proxy(this.handle_iopub_reply,this);
765 782 };
766 783
767 784
768 785 Notebook.prototype.handle_shell_reply = function (e) {
769 786 reply = $.parseJSON(e.data);
770 787 var header = reply.header;
771 788 var content = reply.content;
772 789 var msg_type = header.msg_type;
773 790 // console.log(reply);
774 791 var cell = this.cell_for_msg(reply.parent_header.msg_id);
775 792 if (msg_type === "execute_reply") {
776 793 cell.set_input_prompt(content.execution_count);
777 794 cell.element.removeClass("running");
778 795 this.dirty = true;
779 796 } else if (msg_type === "complete_reply") {
780 797 cell.finish_completing(content.matched_text, content.matches);
781 798 } else if (msg_type === "object_info_reply"){
782 799 //console.log('back from object_info_request : ')
783 800 rep = reply.content;
784 801 if(rep.found)
785 802 {
786 803 cell.finish_tooltip(rep);
787 804 }
788 805 } else {
789 806 //console.log("unknown reply:"+msg_type);
790 807 }
791 808 // when having a rely from object_info_reply,
792 809 // no payload so no nned to handle it
793 810 if(typeof(content.payload)!='undefined') {
794 811 var payload = content.payload || [];
795 812 this.handle_payload(cell, payload);
796 813 }
797 814 };
798 815
799 816
800 817 Notebook.prototype.handle_payload = function (cell, payload) {
801 818 var l = payload.length;
802 819 for (var i=0; i<l; i++) {
803 820 if (payload[i].source === 'IPython.zmq.page.page') {
804 821 if (payload[i].text.trim() !== '') {
805 822 IPython.pager.clear();
806 823 IPython.pager.expand();
807 824 IPython.pager.append_text(payload[i].text);
808 825 }
809 826 } else if (payload[i].source === 'IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input') {
810 827 var index = this.find_cell_index(cell);
811 828 var new_cell = this.insert_code_cell_below(index);
812 829 new_cell.set_code(payload[i].text);
813 830 this.dirty = true;
814 831 }
815 832 };
816 833 };
817 834
818 835
819 836 Notebook.prototype.handle_iopub_reply = function (e) {
820 837 reply = $.parseJSON(e.data);
821 838 var content = reply.content;
822 839 // console.log(reply);
823 840 var msg_type = reply.header.msg_type;
824 841 var cell = this.cell_for_msg(reply.parent_header.msg_id);
825 842 if (msg_type !== 'status' && !cell){
826 843 // message not from this notebook, but should be attached to a cell
827 844 console.log("Received IOPub message not caused by one of my cells");
828 845 console.log(reply);
829 846 return;
830 847 }
831 848 var output_types = ['stream','display_data','pyout','pyerr'];
832 849 if (output_types.indexOf(msg_type) >= 0) {
833 850 this.handle_output(cell, msg_type, content);
834 851 } else if (msg_type === 'status') {
835 852 if (content.execution_state === 'busy') {
836 853 IPython.kernel_status_widget.status_busy();
837 854 } else if (content.execution_state === 'idle') {
838 855 IPython.kernel_status_widget.status_idle();
839 856 } else if (content.execution_state === 'dead') {
840 857 this.handle_status_dead();
841 858 };
842 859 } else if (msg_type === 'clear_output') {
843 860 cell.clear_output(content.stdout, content.stderr, content.other);
844 861 };
845 862 };
846 863
847 864
848 865 Notebook.prototype.handle_status_dead = function () {
849 866 var that = this;
850 867 this.kernel.stop_channels();
851 868 var dialog = $('<div/>');
852 869 dialog.html('The kernel has died, would you like to restart it? If you do not restart the kernel, you will be able to save the notebook, but running code will not work until the notebook is reopened.');
853 870 $(document).append(dialog);
854 871 dialog.dialog({
855 872 resizable: false,
856 873 modal: true,
857 874 title: "Dead kernel",
858 875 buttons : {
859 876 "Restart": function () {
860 877 that.start_kernel();
861 878 $(this).dialog('close');
862 879 },
863 880 "Continue running": function () {
864 881 $(this).dialog('close');
865 882 }
866 883 }
867 884 });
868 885 };
869 886
870 887
871 888 Notebook.prototype.handle_output = function (cell, msg_type, content) {
872 889 var json = {};
873 890 json.output_type = msg_type;
874 891 if (msg_type === "stream") {
875 892 json.text = utils.fixConsole(content.data);
876 893 json.stream = content.name;
877 894 } else if (msg_type === "display_data") {
878 895 json = this.convert_mime_types(json, content.data);
879 896 } else if (msg_type === "pyout") {
880 897 json.prompt_number = content.execution_count;
881 898 json = this.convert_mime_types(json, content.data);
882 899 } else if (msg_type === "pyerr") {
883 900 json.ename = content.ename;
884 901 json.evalue = content.evalue;
885 902 var traceback = [];
886 903 for (var i=0; i<content.traceback.length; i++) {
887 904 traceback.push(utils.fixConsole(content.traceback[i]));
888 905 }
889 906 json.traceback = traceback;
890 907 };
891 908 cell.append_output(json);
892 909 this.dirty = true;
893 910 };
894 911
895 912
896 913 Notebook.prototype.convert_mime_types = function (json, data) {
897 914 if (data['text/plain'] !== undefined) {
898 915 json.text = utils.fixConsole(data['text/plain']);
899 916 };
900 917 if (data['text/html'] !== undefined) {
901 918 json.html = data['text/html'];
902 919 };
903 920 if (data['image/svg+xml'] !== undefined) {
904 921 json.svg = data['image/svg+xml'];
905 922 };
906 923 if (data['image/png'] !== undefined) {
907 924 json.png = data['image/png'];
908 925 };
909 926 if (data['image/jpeg'] !== undefined) {
910 927 json.jpeg = data['image/jpeg'];
911 928 };
912 929 if (data['text/latex'] !== undefined) {
913 930 json.latex = data['text/latex'];
914 931 };
915 932 if (data['application/json'] !== undefined) {
916 933 json.json = data['application/json'];
917 934 };
918 935 if (data['application/javascript'] !== undefined) {
919 936 json.javascript = data['application/javascript'];
920 937 }
921 938 return json;
922 939 };
923 940
924 941
925 942 Notebook.prototype.execute_selected_cell = function (options) {
926 943 // add_new: should a new cell be added if we are at the end of the nb
927 944 // terminal: execute in terminal mode, which stays in the current cell
928 945 default_options = {terminal: false, add_new: true};
929 946 $.extend(default_options, options);
930 947 var that = this;
931 948 var cell = that.selected_cell();
932 949 var cell_index = that.find_cell_index(cell);
933 950 if (cell instanceof IPython.CodeCell) {
934 951 cell.clear_output(true, true, true);
935 952 cell.set_input_prompt('*');
936 953 cell.element.addClass("running");
937 954 var code = cell.get_code();
938 955 var msg_id = that.kernel.execute(cell.get_code());
939 956 that.msg_cell_map[msg_id] = cell.cell_id;
940 957 } else if (cell instanceof IPython.HTMLCell) {
941 958 cell.render();
942 959 }
943 960 if (default_options.terminal) {
944 961 cell.select_all();
945 962 } else {
946 963 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
947 964 that.insert_code_cell_below();
948 965 // If we are adding a new cell at the end, scroll down to show it.
949 966 that.scroll_to_bottom();
950 967 } else {
951 968 that.select(cell_index+1);
952 969 };
953 970 };
954 971 this.dirty = true;
955 972 };
956 973
957 974
958 975 Notebook.prototype.execute_all_cells = function () {
959 976 var ncells = this.ncells();
960 977 for (var i=0; i<ncells; i++) {
961 978 this.select(i);
962 979 this.execute_selected_cell({add_new:false});
963 980 };
964 981 this.scroll_to_bottom();
965 982 };
966 983
967 984
968 985 Notebook.prototype.request_tool_tip = function (cell,func) {
969 986 // Feel free to shorten this logic if you are better
970 987 // than me in regEx
971 988 // basicaly you shoul be able to get xxx.xxx.xxx from
972 989 // something(range(10), kwarg=smth) ; xxx.xxx.xxx( firstarg, rand(234,23), kwarg1=2,
973 990 // remove everything between matchin bracket (need to iterate)
974 991 matchBracket = /\([^\(\)]+\)/g;
975 992 oldfunc = func;
976 993 func = func.replace(matchBracket,"");
977 994 while( oldfunc != func )
978 995 {
979 996 oldfunc = func;
980 997 func = func.replace(matchBracket,"");
981 998 }
982 999 // remove everythin after last open bracket
983 1000 endBracket = /\([^\(]*$/g;
984 1001 func = func.replace(endBracket,"");
985 1002 var re = /[a-zA-Z._]+$/g;
986 1003 var msg_id = this.kernel.object_info_request(re.exec(func));
987 1004 if(typeof(msg_id)!='undefined'){
988 1005 this.msg_cell_map[msg_id] = cell.cell_id;
989 1006 }
990 1007 };
991 1008
992 1009 Notebook.prototype.complete_cell = function (cell, line, cursor_pos) {
993 1010 var msg_id = this.kernel.complete(line, cursor_pos);
994 1011 this.msg_cell_map[msg_id] = cell.cell_id;
995 1012 };
996 1013
997 1014 // Persistance and loading
998 1015
999 1016
1000 1017 Notebook.prototype.fromJSON = function (data) {
1001 1018 var ncells = this.ncells();
1002 1019 var i;
1003 1020 for (i=0; i<ncells; i++) {
1004 1021 // Always delete cell 0 as they get renumbered as they are deleted.
1005 1022 this.delete_cell(0);
1006 1023 };
1007 1024 // Save the metadata
1008 1025 this.metadata = data.metadata;
1009 1026 // Only handle 1 worksheet for now.
1010 1027 var worksheet = data.worksheets[0];
1011 1028 if (worksheet !== undefined) {
1012 1029 var new_cells = worksheet.cells;
1013 1030 ncells = new_cells.length;
1014 1031 var cell_data = null;
1015 1032 var new_cell = null;
1016 1033 for (i=0; i<ncells; i++) {
1017 1034 cell_data = new_cells[i];
1018 1035 if (cell_data.cell_type == 'code') {
1019 1036 new_cell = this.insert_code_cell_below();
1020 1037 new_cell.fromJSON(cell_data);
1021 1038 } else if (cell_data.cell_type === 'html') {
1022 1039 new_cell = this.insert_html_cell_below();
1023 1040 new_cell.fromJSON(cell_data);
1024 1041 } else if (cell_data.cell_type === 'markdown') {
1025 1042 new_cell = this.insert_markdown_cell_below();
1026 1043 new_cell.fromJSON(cell_data);
1027 1044 };
1028 1045 };
1029 1046 };
1030 1047 };
1031 1048
1032 1049
1033 1050 Notebook.prototype.toJSON = function () {
1034 1051 var cells = this.cells();
1035 1052 var ncells = cells.length;
1036 1053 cell_array = new Array(ncells);
1037 1054 for (var i=0; i<ncells; i++) {
1038 1055 cell_array[i] = cells[i].toJSON();
1039 1056 };
1040 1057 data = {
1041 1058 // Only handle 1 worksheet for now.
1042 1059 worksheets : [{cells:cell_array}],
1043 1060 metadata : this.metadata
1044 1061 };
1045 1062 return data;
1046 1063 };
1047 1064
1048 1065 Notebook.prototype.save_notebook = function () {
1049 1066 if (IPython.save_widget.test_notebook_name()) {
1050 1067 var notebook_id = IPython.save_widget.get_notebook_id();
1051 1068 var nbname = IPython.save_widget.get_notebook_name();
1052 1069 // We may want to move the name/id/nbformat logic inside toJSON?
1053 1070 var data = this.toJSON();
1054 1071 data.metadata.name = nbname;
1055 1072 data.nbformat = 2;
1056 1073 // We do the call with settings so we can set cache to false.
1057 1074 var settings = {
1058 1075 processData : false,
1059 1076 cache : false,
1060 1077 type : "PUT",
1061 1078 data : JSON.stringify(data),
1062 1079 headers : {'Content-Type': 'application/json'},
1063 1080 success : $.proxy(this.notebook_saved,this),
1064 1081 error : $.proxy(this.notebook_save_failed,this)
1065 1082 };
1066 1083 IPython.save_widget.status_saving();
1067 1084 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1068 1085 $.ajax(url, settings);
1069 1086 };
1070 1087 };
1071 1088
1072 1089
1073 1090 Notebook.prototype.notebook_saved = function (data, status, xhr) {
1074 1091 this.dirty = false;
1075 1092 IPython.save_widget.notebook_saved();
1076 1093 IPython.save_widget.status_last_saved();
1077 1094 };
1078 1095
1079 1096
1080 1097 Notebook.prototype.notebook_save_failed = function (xhr, status, error_msg) {
1081 1098 IPython.save_widget.status_save_failed();
1082 1099 };
1083 1100
1084 1101
1085 1102 Notebook.prototype.load_notebook = function (callback) {
1086 1103 var that = this;
1087 1104 var notebook_id = IPython.save_widget.get_notebook_id();
1088 1105 // We do the call with settings so we can set cache to false.
1089 1106 var settings = {
1090 1107 processData : false,
1091 1108 cache : false,
1092 1109 type : "GET",
1093 1110 dataType : "json",
1094 1111 success : function (data, status, xhr) {
1095 1112 that.notebook_loaded(data, status, xhr);
1096 1113 if (callback !== undefined) {
1097 1114 callback();
1098 1115 };
1099 1116 }
1100 1117 };
1101 1118 IPython.save_widget.status_loading();
1102 1119 var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id;
1103 1120 $.ajax(url, settings);
1104 1121 };
1105 1122
1106 1123
1107 1124 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
1108 1125 var allowed = xhr.getResponseHeader('Allow');
1109 1126 this.fromJSON(data);
1110 1127 if (this.ncells() === 0) {
1111 1128 this.insert_code_cell_below();
1112 1129 };
1113 1130 IPython.save_widget.status_last_saved();
1114 1131 IPython.save_widget.set_notebook_name(data.metadata.name);
1115 1132 this.dirty = false;
1116 1133 if (! this.read_only) {
1117 1134 this.start_kernel();
1118 1135 }
1119 1136 // fromJSON always selects the last cell inserted. We need to wait
1120 1137 // until that is done before scrolling to the top.
1121 1138 setTimeout(function () {
1122 1139 IPython.notebook.select(0);
1123 1140 IPython.notebook.scroll_to_top();
1124 1141 }, 50);
1125 1142 };
1126 1143
1127 1144 IPython.Notebook = Notebook;
1128 1145
1129 1146
1130 1147 return IPython;
1131 1148
1132 1149 }(IPython));
1133 1150
General Comments 0
You need to be logged in to leave comments. Login now