##// END OF EJS Templates
Some typo fixes
Jonathan Frederic -
Show More
@@ -1,2387 +1,2387 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 /**
5 5 * @module notebook
6 6 */
7 7 define([
8 8 'base/js/namespace',
9 9 'jquery',
10 10 'base/js/utils',
11 11 'base/js/dialog',
12 12 'notebook/js/cell',
13 13 'notebook/js/textcell',
14 14 'notebook/js/codecell',
15 15 'services/sessions/session',
16 16 'notebook/js/celltoolbar',
17 17 'components/marked/lib/marked',
18 18 'codemirror/lib/codemirror',
19 19 'codemirror/addon/runmode/runmode',
20 20 'notebook/js/mathjaxutils',
21 21 'base/js/keyboard',
22 22 'notebook/js/tooltip',
23 23 'notebook/js/celltoolbarpresets/default',
24 24 'notebook/js/celltoolbarpresets/rawcell',
25 25 'notebook/js/celltoolbarpresets/slideshow',
26 26 'notebook/js/scrollmanager'
27 27 ], function (
28 28 IPython,
29 29 $,
30 30 utils,
31 31 dialog,
32 32 cellmod,
33 33 textcell,
34 34 codecell,
35 35 session,
36 36 celltoolbar,
37 37 marked,
38 38 CodeMirror,
39 39 runMode,
40 40 mathjaxutils,
41 41 keyboard,
42 42 tooltip,
43 43 default_celltoolbar,
44 44 rawcell_celltoolbar,
45 45 slideshow_celltoolbar,
46 46 scrollmanager
47 47 ) {
48 48 "use strict";
49 49
50 50 /**
51 51 * Contains and manages cells.
52 52 * @class Notebook
53 53 * @param {String} selector
54 54 * @param {dictionary} options - Dictionary of keyword arguments.
55 55 * events: $(Events) instance
56 56 * keyboard_manager: KeyboardManager instance
57 57 * contents: Contents instance
58 58 * save_widget: SaveWidget instance
59 59 * config: dictionary
60 60 * base_url : string
61 61 * notebook_path : string
62 62 * notebook_name : string
63 63 */
64 64 var Notebook = function (selector, options) {
65 65 this.config = utils.mergeopt(Notebook, options.config);
66 66 this.base_url = options.base_url;
67 67 this.notebook_path = options.notebook_path;
68 68 this.notebook_name = options.notebook_name;
69 69 this.events = options.events;
70 70 this.keyboard_manager = options.keyboard_manager;
71 71 this.contents = options.contents;
72 72 this.save_widget = options.save_widget;
73 73 this.tooltip = new tooltip.Tooltip(this.events);
74 74 this.ws_url = options.ws_url;
75 75 this._session_starting = false;
76 76 this.default_cell_type = this.config.default_cell_type || 'code';
77 77
78 78 // Create default scroll manager.
79 79 this.scroll_manager = new scrollmanager.ScrollManager(this);
80 80
81 81 // TODO: This code smells (and the other `= this` line a couple lines down)
82 82 // We need a better way to deal with circular instance references.
83 83 this.keyboard_manager.notebook = this;
84 84 this.save_widget.notebook = this;
85 85
86 86 mathjaxutils.init();
87 87
88 88 if (marked) {
89 89 marked.setOptions({
90 90 gfm : true,
91 91 tables: true,
92 92 // FIXME: probably want central config for CodeMirror theme when we have js config
93 93 langPrefix: "cm-s-ipython language-",
94 94 highlight: function(code, lang, callback) {
95 95 if (!lang) {
96 96 // no language, no highlight
97 97 if (callback) {
98 98 callback(null, code);
99 99 return;
100 100 } else {
101 101 return code;
102 102 }
103 103 }
104 104 utils.requireCodeMirrorMode(lang, function (spec) {
105 105 var el = document.createElement("div");
106 106 var mode = CodeMirror.getMode({}, spec);
107 107 if (!mode) {
108 108 console.log("No CodeMirror mode: " + lang);
109 109 callback(null, code);
110 110 return;
111 111 }
112 112 try {
113 113 CodeMirror.runMode(code, spec, el);
114 114 callback(null, el.innerHTML);
115 115 } catch (err) {
116 116 console.log("Failed to highlight " + lang + " code", err);
117 117 callback(err, code);
118 118 }
119 119 }, function (err) {
120 120 console.log("No CodeMirror mode: " + lang);
121 121 callback(err, code);
122 122 });
123 123 }
124 124 });
125 125 }
126 126
127 127 this.element = $(selector);
128 128 this.element.scroll();
129 129 this.element.data("notebook", this);
130 130 this.next_prompt_number = 1;
131 131 this.session = null;
132 132 this.kernel = null;
133 133 this.clipboard = null;
134 134 this.undelete_backup = null;
135 135 this.undelete_index = null;
136 136 this.undelete_below = false;
137 137 this.paste_enabled = false;
138 138 this.writable = false;
139 139 // It is important to start out in command mode to match the intial mode
140 140 // of the KeyboardManager.
141 141 this.mode = 'command';
142 142 this.set_dirty(false);
143 143 this.metadata = {};
144 144 this._checkpoint_after_save = false;
145 145 this.last_checkpoint = null;
146 146 this.checkpoints = [];
147 147 this.autosave_interval = 0;
148 148 this.autosave_timer = null;
149 149 // autosave *at most* every two minutes
150 150 this.minimum_autosave_interval = 120000;
151 151 this.notebook_name_blacklist_re = /[\/\\:]/;
152 152 this.nbformat = 4; // Increment this when changing the nbformat
153 153 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
154 154 this.codemirror_mode = 'ipython';
155 155 this.create_elements();
156 156 this.bind_events();
157 157 this.kernel_selector = null;
158 158 this.dirty = null;
159 159 this.trusted = null;
160 160 this._fully_loaded = false;
161 161
162 162 // Trigger cell toolbar registration.
163 163 default_celltoolbar.register(this);
164 164 rawcell_celltoolbar.register(this);
165 165 slideshow_celltoolbar.register(this);
166 166
167 167 // prevent assign to miss-typed properties.
168 168 Object.seal(this);
169 169 };
170 170
171 171 Notebook.options_default = {
172 172 // can be any cell type, or the special values of
173 173 // 'above', 'below', or 'selected' to get the value from another cell.
174 174 Notebook: {
175 175 default_cell_type: 'code'
176 176 }
177 177 };
178 178
179 179 /**
180 180 * Create an HTML and CSS representation of the notebook.
181 181 */
182 182 Notebook.prototype.create_elements = function () {
183 183 var that = this;
184 184 this.element.attr('tabindex','-1');
185 185 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
186 186 // We add this end_space div to the end of the notebook div to:
187 187 // i) provide a margin between the last cell and the end of the notebook
188 188 // ii) to prevent the div from scrolling up when the last cell is being
189 189 // edited, but is too low on the page, which browsers will do automatically.
190 190 var end_space = $('<div/>').addClass('end_space');
191 191 end_space.dblclick(function (e) {
192 192 var ncells = that.ncells();
193 193 that.insert_cell_below('code',ncells-1);
194 194 });
195 195 this.element.append(this.container);
196 196 this.container.append(end_space);
197 197 };
198 198
199 199 /**
200 200 * Bind JavaScript events: key presses and custom IPython events.
201 201 */
202 202 Notebook.prototype.bind_events = function () {
203 203 var that = this;
204 204
205 205 this.events.on('set_next_input.Notebook', function (event, data) {
206 206 if (data.replace) {
207 207 data.cell.set_text(data.text);
208 208 data.cell.clear_output();
209 209 } else {
210 210 var index = that.find_cell_index(data.cell);
211 211 var new_cell = that.insert_cell_below('code',index);
212 212 new_cell.set_text(data.text);
213 213 }
214 214 that.dirty = true;
215 215 });
216 216
217 217 this.events.on('unrecognized_cell.Cell', function () {
218 218 that.warn_nbformat_minor();
219 219 });
220 220
221 221 this.events.on('unrecognized_output.OutputArea', function () {
222 222 that.warn_nbformat_minor();
223 223 });
224 224
225 225 this.events.on('set_dirty.Notebook', function (event, data) {
226 226 that.dirty = data.value;
227 227 });
228 228
229 229 this.events.on('trust_changed.Notebook', function (event, trusted) {
230 230 that.trusted = trusted;
231 231 });
232 232
233 233 this.events.on('select.Cell', function (event, data) {
234 234 var index = that.find_cell_index(data.cell);
235 235 that.select(index);
236 236 });
237 237
238 238 this.events.on('edit_mode.Cell', function (event, data) {
239 239 that.handle_edit_mode(data.cell);
240 240 });
241 241
242 242 this.events.on('command_mode.Cell', function (event, data) {
243 243 that.handle_command_mode(data.cell);
244 244 });
245 245
246 246 this.events.on('spec_changed.Kernel', function(event, data) {
247 247 that.metadata.kernelspec =
248 248 {name: data.name, display_name: data.display_name};
249 249 });
250 250
251 251 this.events.on('kernel_ready.Kernel', function(event, data) {
252 252 var kinfo = data.kernel.info_reply;
253 253 var langinfo = kinfo.language_info || {};
254 254 that.metadata.language_info = langinfo;
255 255 // Mode 'null' should be plain, unhighlighted text.
256 256 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
257 257 that.set_codemirror_mode(cm_mode);
258 258 });
259 259
260 260 var collapse_time = function (time) {
261 261 var app_height = $('#ipython-main-app').height(); // content height
262 262 var splitter_height = $('div#pager_splitter').outerHeight(true);
263 263 var new_height = app_height - splitter_height;
264 264 that.element.animate({height : new_height + 'px'}, time);
265 265 };
266 266
267 267 this.element.bind('collapse_pager', function (event, extrap) {
268 268 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
269 269 collapse_time(time);
270 270 });
271 271
272 272 var expand_time = function (time) {
273 273 var app_height = $('#ipython-main-app').height(); // content height
274 274 var splitter_height = $('div#pager_splitter').outerHeight(true);
275 275 var pager_height = $('div#pager').outerHeight(true);
276 276 var new_height = app_height - pager_height - splitter_height;
277 277 that.element.animate({height : new_height + 'px'}, time);
278 278 };
279 279
280 280 this.element.bind('expand_pager', function (event, extrap) {
281 281 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
282 282 expand_time(time);
283 283 });
284 284
285 285 // Firefox 22 broke $(window).on("beforeunload")
286 286 // I'm not sure why or how.
287 287 window.onbeforeunload = function (e) {
288 288 // TODO: Make killing the kernel configurable.
289 289 var kill_kernel = false;
290 290 if (kill_kernel) {
291 291 that.session.delete();
292 292 }
293 293 // if we are autosaving, trigger an autosave on nav-away.
294 294 // still warn, because if we don't the autosave may fail.
295 295 if (that.dirty) {
296 296 if ( that.autosave_interval ) {
297 297 // schedule autosave in a timeout
298 298 // this gives you a chance to forcefully discard changes
299 299 // by reloading the page if you *really* want to.
300 300 // the timer doesn't start until you *dismiss* the dialog.
301 301 setTimeout(function () {
302 302 if (that.dirty) {
303 303 that.save_notebook();
304 304 }
305 305 }, 1000);
306 306 return "Autosave in progress, latest changes may be lost.";
307 307 } else {
308 308 return "Unsaved changes will be lost.";
309 309 }
310 310 }
311 311 // Null is the *only* return value that will make the browser not
312 312 // pop up the "don't leave" dialog.
313 313 return null;
314 314 };
315 315 };
316 316
317 317 Notebook.prototype.warn_nbformat_minor = function (event) {
318 318 /**
319 319 * trigger a warning dialog about missing functionality from newer minor versions
320 320 */
321 321 var v = 'v' + this.nbformat + '.';
322 322 var orig_vs = v + this.nbformat_minor;
323 323 var this_vs = v + this.current_nbformat_minor;
324 324 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
325 325 this_vs + ". You can still work with this notebook, but cell and output types " +
326 326 "introduced in later notebook versions will not be available.";
327 327
328 328 dialog.modal({
329 329 notebook: this,
330 330 keyboard_manager: this.keyboard_manager,
331 331 title : "Newer Notebook",
332 332 body : msg,
333 333 buttons : {
334 334 OK : {
335 335 "class" : "btn-danger"
336 336 }
337 337 }
338 338 });
339 339 }
340 340
341 341 /**
342 342 * Set the dirty flag, and trigger the set_dirty.Notebook event
343 343 */
344 344 Notebook.prototype.set_dirty = function (value) {
345 345 if (value === undefined) {
346 346 value = true;
347 347 }
348 348 if (this.dirty == value) {
349 349 return;
350 350 }
351 351 this.events.trigger('set_dirty.Notebook', {value: value});
352 352 };
353 353
354 354 /**
355 355 * Scroll the top of the page to a given cell.
356 356 *
357 357 * @param {Number} cell_number An index of the cell to view
358 358 * @param {Number} time Animation time in milliseconds
359 359 * @return {Number} Pixel offset from the top of the container
360 360 */
361 361 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
362 362 var cells = this.get_cells();
363 363 time = time || 0;
364 364 cell_number = Math.min(cells.length-1,cell_number);
365 365 cell_number = Math.max(0 ,cell_number);
366 366 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
367 367 this.element.animate({scrollTop:scroll_value}, time);
368 368 return scroll_value;
369 369 };
370 370
371 371 /**
372 372 * Scroll to the bottom of the page.
373 373 */
374 374 Notebook.prototype.scroll_to_bottom = function () {
375 375 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
376 376 };
377 377
378 378 /**
379 379 * Scroll to the top of the page.
380 380 */
381 381 Notebook.prototype.scroll_to_top = function () {
382 382 this.element.animate({scrollTop:0}, 0);
383 383 };
384 384
385 385 // Edit Notebook metadata
386 386
387 387 Notebook.prototype.edit_metadata = function () {
388 388 var that = this;
389 389 dialog.edit_metadata({
390 390 md: this.metadata,
391 391 callback: function (md) {
392 392 that.metadata = md;
393 393 },
394 394 name: 'Notebook',
395 395 notebook: this,
396 396 keyboard_manager: this.keyboard_manager});
397 397 };
398 398
399 399 // Cell indexing, retrieval, etc.
400 400
401 401 /**
402 402 * Get all cell elements in the notebook.
403 403 *
404 404 * @return {jQuery} A selector of all cell elements
405 405 */
406 406 Notebook.prototype.get_cell_elements = function () {
407 407 return this.container.find(".cell").not('.cell .cell');
408 408 };
409 409
410 410 /**
411 411 * Get a particular cell element.
412 412 *
413 413 * @param {Number} index An index of a cell to select
414 414 * @return {jQuery} A selector of the given cell.
415 415 */
416 416 Notebook.prototype.get_cell_element = function (index) {
417 417 var result = null;
418 418 var e = this.get_cell_elements().eq(index);
419 419 if (e.length !== 0) {
420 420 result = e;
421 421 }
422 422 return result;
423 423 };
424 424
425 425 /**
426 426 * Try to get a particular cell by msg_id.
427 427 *
428 428 * @param {String} msg_id A message UUID
429 429 * @return {Cell} Cell or null if no cell was found.
430 430 */
431 431 Notebook.prototype.get_msg_cell = function (msg_id) {
432 432 return codecell.CodeCell.msg_cells[msg_id] || null;
433 433 };
434 434
435 435 /**
436 436 * Count the cells in this notebook.
437 437 *
438 438 * @return {Number} The number of cells in this notebook
439 439 */
440 440 Notebook.prototype.ncells = function () {
441 441 return this.get_cell_elements().length;
442 442 };
443 443
444 444 /**
445 445 * Get all Cell objects in this notebook.
446 446 *
447 447 * @return {Array} This notebook's Cell objects
448 448 */
449 449 // TODO: we are often calling cells as cells()[i], which we should optimize
450 450 // to cells(i) or a new method.
451 451 Notebook.prototype.get_cells = function () {
452 452 return this.get_cell_elements().toArray().map(function (e) {
453 453 return $(e).data("cell");
454 454 });
455 455 };
456 456
457 457 /**
458 458 * Get a Cell object from this notebook.
459 459 *
460 460 * @param {Number} index An index of a cell to retrieve
461 461 * @return {Cell} Cell or null if no cell was found.
462 462 */
463 463 Notebook.prototype.get_cell = function (index) {
464 464 var result = null;
465 465 var ce = this.get_cell_element(index);
466 466 if (ce !== null) {
467 467 result = ce.data('cell');
468 468 }
469 469 return result;
470 470 };
471 471
472 472 /**
473 473 * Get the cell below a given cell.
474 474 *
475 475 * @param {Cell} cell The provided cell
476 476 * @return {Cell} the next cell or null if no cell was found.
477 477 */
478 478 Notebook.prototype.get_next_cell = function (cell) {
479 479 var result = null;
480 480 var index = this.find_cell_index(cell);
481 481 if (this.is_valid_cell_index(index+1)) {
482 482 result = this.get_cell(index+1);
483 483 }
484 484 return result;
485 485 };
486 486
487 487 /**
488 488 * Get the cell above a given cell.
489 489 *
490 490 * @param {Cell} cell The provided cell
491 491 * @return {Cell} The previous cell or null if no cell was found.
492 492 */
493 493 Notebook.prototype.get_prev_cell = function (cell) {
494 494 var result = null;
495 495 var index = this.find_cell_index(cell);
496 496 if (index !== null && index > 0) {
497 497 result = this.get_cell(index-1);
498 498 }
499 499 return result;
500 500 };
501 501
502 502 /**
503 503 * Get the numeric index of a given cell.
504 504 *
505 505 * @param {Cell} cell The provided cell
506 506 * @return {Number} The cell's numeric index or null if no cell was found.
507 507 */
508 508 Notebook.prototype.find_cell_index = function (cell) {
509 509 var result = null;
510 510 this.get_cell_elements().filter(function (index) {
511 511 if ($(this).data("cell") === cell) {
512 512 result = index;
513 513 }
514 514 });
515 515 return result;
516 516 };
517 517
518 518 /**
519 519 * Get a given index , or the selected index if none is provided.
520 520 *
521 521 * @param {Number} index A cell's index
522 522 * @return {Number} The given index, or selected index if none is provided.
523 523 */
524 524 Notebook.prototype.index_or_selected = function (index) {
525 525 var i;
526 526 if (index === undefined || index === null) {
527 527 i = this.get_selected_index();
528 528 if (i === null) {
529 529 i = 0;
530 530 }
531 531 } else {
532 532 i = index;
533 533 }
534 534 return i;
535 535 };
536 536
537 537 /**
538 538 * Get the currently selected cell.
539 539 * @return {Cell} The selected cell
540 540 */
541 541 Notebook.prototype.get_selected_cell = function () {
542 542 var index = this.get_selected_index();
543 543 return this.get_cell(index);
544 544 };
545 545
546 546 /**
547 547 * Check whether a cell index is valid.
548 548 *
549 549 * @param {Number} index A cell index
550 550 * @return True if the index is valid, false otherwise
551 551 */
552 552 Notebook.prototype.is_valid_cell_index = function (index) {
553 553 if (index !== null && index >= 0 && index < this.ncells()) {
554 554 return true;
555 555 } else {
556 556 return false;
557 557 }
558 558 };
559 559
560 560 /**
561 561 * Get the index of the currently selected cell.
562 562
563 563 * @return {Number} The selected cell's numeric index
564 564 */
565 565 Notebook.prototype.get_selected_index = function () {
566 566 var result = null;
567 567 this.get_cell_elements().filter(function (index) {
568 568 if ($(this).data("cell").selected === true) {
569 569 result = index;
570 570 }
571 571 });
572 572 return result;
573 573 };
574 574
575 575
576 576 // Cell selection.
577 577
578 578 /**
579 579 * Programmatically select a cell.
580 580 *
581 581 * @param {Number} index A cell's index
582 582 * @return {Notebook} This notebook
583 583 */
584 584 Notebook.prototype.select = function (index) {
585 585 if (this.is_valid_cell_index(index)) {
586 586 var sindex = this.get_selected_index();
587 587 if (sindex !== null && index !== sindex) {
588 588 // If we are about to select a different cell, make sure we are
589 589 // first in command mode.
590 590 if (this.mode !== 'command') {
591 591 this.command_mode();
592 592 }
593 593 this.get_cell(sindex).unselect();
594 594 }
595 595 var cell = this.get_cell(index);
596 596 cell.select();
597 597 if (cell.cell_type === 'heading') {
598 598 this.events.trigger('selected_cell_type_changed.Notebook',
599 599 {'cell_type':cell.cell_type,level:cell.level}
600 600 );
601 601 } else {
602 602 this.events.trigger('selected_cell_type_changed.Notebook',
603 603 {'cell_type':cell.cell_type}
604 604 );
605 605 }
606 606 }
607 607 return this;
608 608 };
609 609
610 610 /**
611 611 * Programmatically select the next cell.
612 612 *
613 613 * @return {Notebook} This notebook
614 614 */
615 615 Notebook.prototype.select_next = function () {
616 616 var index = this.get_selected_index();
617 617 this.select(index+1);
618 618 return this;
619 619 };
620 620
621 621 /**
622 622 * Programmatically select the previous cell.
623 623 *
624 624 * @return {Notebook} This notebook
625 625 */
626 626 Notebook.prototype.select_prev = function () {
627 627 var index = this.get_selected_index();
628 628 this.select(index-1);
629 629 return this;
630 630 };
631 631
632 632
633 633 // Edit/Command mode
634 634
635 635 /**
636 636 * Gets the index of the cell that is in edit mode.
637 637 *
638 638 * @return index {int}
639 639 **/
640 640 Notebook.prototype.get_edit_index = function () {
641 641 var result = null;
642 642 this.get_cell_elements().filter(function (index) {
643 643 if ($(this).data("cell").mode === 'edit') {
644 644 result = index;
645 645 }
646 646 });
647 647 return result;
648 648 };
649 649
650 650 /**
651 651 * Handle when a a cell blurs and the notebook should enter command mode.
652 652 *
653 653 * @param [cell] {Cell} Cell to enter command mode on.
654 654 **/
655 655 Notebook.prototype.handle_command_mode = function (cell) {
656 656 if (this.mode !== 'command') {
657 657 cell.command_mode();
658 658 this.mode = 'command';
659 659 this.events.trigger('command_mode.Notebook');
660 660 this.keyboard_manager.command_mode();
661 661 }
662 662 };
663 663
664 664 /**
665 665 * Make the notebook enter command mode.
666 666 **/
667 667 Notebook.prototype.command_mode = function () {
668 668 var cell = this.get_cell(this.get_edit_index());
669 669 if (cell && this.mode !== 'command') {
670 670 // We don't call cell.command_mode, but rather call cell.focus_cell()
671 671 // which will blur and CM editor and trigger the call to
672 672 // handle_command_mode.
673 673 cell.focus_cell();
674 674 }
675 675 };
676 676
677 677 /**
678 678 * Handle when a cell fires it's edit_mode event.
679 679 *
680 680 * @param [cell] {Cell} Cell to enter edit mode on.
681 681 **/
682 682 Notebook.prototype.handle_edit_mode = function (cell) {
683 683 if (cell && this.mode !== 'edit') {
684 684 cell.edit_mode();
685 685 this.mode = 'edit';
686 686 this.events.trigger('edit_mode.Notebook');
687 687 this.keyboard_manager.edit_mode();
688 688 }
689 689 };
690 690
691 691 /**
692 692 * Make a cell enter edit mode.
693 693 **/
694 694 Notebook.prototype.edit_mode = function () {
695 695 var cell = this.get_selected_cell();
696 696 if (cell && this.mode !== 'edit') {
697 697 cell.unrender();
698 698 cell.focus_editor();
699 699 }
700 700 };
701 701
702 702 /**
703 703 * Focus the currently selected cell.
704 704 **/
705 705 Notebook.prototype.focus_cell = function () {
706 706 var cell = this.get_selected_cell();
707 707 if (cell === null) {return;} // No cell is selected
708 708 cell.focus_cell();
709 709 };
710 710
711 711 // Cell movement
712 712
713 713 /**
714 714 * Move given (or selected) cell up and select it.
715 715 *
716 716 * @param [index] {integer} cell index
717 717 * @return {Notebook} This notebook
718 718 **/
719 719 Notebook.prototype.move_cell_up = function (index) {
720 720 var i = this.index_or_selected(index);
721 721 if (this.is_valid_cell_index(i) && i > 0) {
722 722 var pivot = this.get_cell_element(i-1);
723 723 var tomove = this.get_cell_element(i);
724 724 if (pivot !== null && tomove !== null) {
725 725 tomove.detach();
726 726 pivot.before(tomove);
727 727 this.select(i-1);
728 728 var cell = this.get_selected_cell();
729 729 cell.focus_cell();
730 730 }
731 731 this.set_dirty(true);
732 732 }
733 733 return this;
734 734 };
735 735
736 736
737 737 /**
738 738 * Move given (or selected) cell down and select it
739 739 *
740 740 * @param [index] {integer} cell index
741 741 * @return {Notebook} This notebook
742 742 **/
743 743 Notebook.prototype.move_cell_down = function (index) {
744 744 var i = this.index_or_selected(index);
745 745 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
746 746 var pivot = this.get_cell_element(i+1);
747 747 var tomove = this.get_cell_element(i);
748 748 if (pivot !== null && tomove !== null) {
749 749 tomove.detach();
750 750 pivot.after(tomove);
751 751 this.select(i+1);
752 752 var cell = this.get_selected_cell();
753 753 cell.focus_cell();
754 754 }
755 755 }
756 756 this.set_dirty();
757 757 return this;
758 758 };
759 759
760 760
761 761 // Insertion, deletion.
762 762
763 763 /**
764 764 * Delete a cell from the notebook.
765 765 *
766 766 * @param [index] A cell's numeric index
767 767 * @return {Notebook} This notebook
768 768 */
769 769 Notebook.prototype.delete_cell = function (index) {
770 770 var i = this.index_or_selected(index);
771 771 var cell = this.get_cell(i);
772 772 if (!cell.is_deletable()) {
773 773 return this;
774 774 }
775 775
776 776 this.undelete_backup = cell.toJSON();
777 777 $('#undelete_cell').removeClass('disabled');
778 778 if (this.is_valid_cell_index(i)) {
779 779 var old_ncells = this.ncells();
780 780 var ce = this.get_cell_element(i);
781 781 ce.remove();
782 782 if (i === 0) {
783 783 // Always make sure we have at least one cell.
784 784 if (old_ncells === 1) {
785 785 this.insert_cell_below('code');
786 786 }
787 787 this.select(0);
788 788 this.undelete_index = 0;
789 789 this.undelete_below = false;
790 790 } else if (i === old_ncells-1 && i !== 0) {
791 791 this.select(i-1);
792 792 this.undelete_index = i - 1;
793 793 this.undelete_below = true;
794 794 } else {
795 795 this.select(i);
796 796 this.undelete_index = i;
797 797 this.undelete_below = false;
798 798 }
799 799 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
800 800 this.set_dirty(true);
801 801 }
802 802 return this;
803 803 };
804 804
805 805 /**
806 806 * Restore the most recently deleted cell.
807 807 */
808 808 Notebook.prototype.undelete_cell = function() {
809 809 if (this.undelete_backup !== null && this.undelete_index !== null) {
810 810 var current_index = this.get_selected_index();
811 811 if (this.undelete_index < current_index) {
812 812 current_index = current_index + 1;
813 813 }
814 814 if (this.undelete_index >= this.ncells()) {
815 815 this.select(this.ncells() - 1);
816 816 }
817 817 else {
818 818 this.select(this.undelete_index);
819 819 }
820 820 var cell_data = this.undelete_backup;
821 821 var new_cell = null;
822 822 if (this.undelete_below) {
823 823 new_cell = this.insert_cell_below(cell_data.cell_type);
824 824 } else {
825 825 new_cell = this.insert_cell_above(cell_data.cell_type);
826 826 }
827 827 new_cell.fromJSON(cell_data);
828 828 if (this.undelete_below) {
829 829 this.select(current_index+1);
830 830 } else {
831 831 this.select(current_index);
832 832 }
833 833 this.undelete_backup = null;
834 834 this.undelete_index = null;
835 835 }
836 836 $('#undelete_cell').addClass('disabled');
837 837 };
838 838
839 839 /**
840 840 * Insert a cell so that after insertion the cell is at given index.
841 841 *
842 842 * If cell type is not provided, it will default to the type of the
843 843 * currently active cell.
844 844 *
845 845 * Similar to insert_above, but index parameter is mandatory
846 846 *
847 847 * Index will be brought back into the accessible range [0,n]
848 848 *
849 849 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
850 850 * @param [index] {int} a valid index where to insert cell
851 851 *
852 852 * @return cell {cell|null} created cell or null
853 853 **/
854 854 Notebook.prototype.insert_cell_at_index = function(type, index){
855 855
856 856 var ncells = this.ncells();
857 857 index = Math.min(index, ncells);
858 858 index = Math.max(index, 0);
859 859 var cell = null;
860 860 type = type || this.default_cell_type;
861 861 if (type === 'above') {
862 862 if (index > 0) {
863 863 type = this.get_cell(index-1).cell_type;
864 864 } else {
865 865 type = 'code';
866 866 }
867 867 } else if (type === 'below') {
868 868 if (index < ncells) {
869 869 type = this.get_cell(index).cell_type;
870 870 } else {
871 871 type = 'code';
872 872 }
873 873 } else if (type === 'selected') {
874 874 type = this.get_selected_cell().cell_type;
875 875 }
876 876
877 877 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
878 878 var cell_options = {
879 879 events: this.events,
880 880 config: this.config,
881 881 keyboard_manager: this.keyboard_manager,
882 882 notebook: this,
883 883 tooltip: this.tooltip
884 884 };
885 885 switch(type) {
886 886 case 'code':
887 887 cell = new codecell.CodeCell(this.kernel, cell_options);
888 888 cell.set_input_prompt();
889 889 break;
890 890 case 'markdown':
891 891 cell = new textcell.MarkdownCell(cell_options);
892 892 break;
893 893 case 'raw':
894 894 cell = new textcell.RawCell(cell_options);
895 895 break;
896 896 default:
897 897 console.log("Unrecognized cell type: ", type, cellmod);
898 898 cell = new cellmod.UnrecognizedCell(cell_options);
899 899 }
900 900
901 901 if(this._insert_element_at_index(cell.element,index)) {
902 902 cell.render();
903 903 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
904 904 cell.refresh();
905 905 // We used to select the cell after we refresh it, but there
906 906 // are now cases were this method is called where select is
907 907 // not appropriate. The selection logic should be handled by the
908 908 // caller of the the top level insert_cell methods.
909 909 this.set_dirty(true);
910 910 }
911 911 }
912 912 return cell;
913 913
914 914 };
915 915
916 916 /**
917 917 * Insert an element at given cell index.
918 918 * return true if everything whent fine.
919 919 *
920 920 * @param element {dom_element} a cell element
921 921 * @param [index] {int} a valid index where to inser cell
922 922 * @private
923 923 **/
924 924 Notebook.prototype._insert_element_at_index = function(element, index){
925 925 if (element === undefined){
926 926 return false;
927 927 }
928 928
929 929 var ncells = this.ncells();
930 930
931 931 if (ncells === 0) {
932 932 // special case append if empty
933 933 this.element.find('div.end_space').before(element);
934 934 } else if ( ncells === index ) {
935 935 // special case append it the end, but not empty
936 936 this.get_cell_element(index-1).after(element);
937 937 } else if (this.is_valid_cell_index(index)) {
938 938 // otherwise always somewhere to append to
939 939 this.get_cell_element(index).before(element);
940 940 } else {
941 941 return false;
942 942 }
943 943
944 944 if (this.undelete_index !== null && index <= this.undelete_index) {
945 945 this.undelete_index = this.undelete_index + 1;
946 946 this.set_dirty(true);
947 947 }
948 948 return true;
949 949 };
950 950
951 951 /**
952 952 * Insert a cell of given type above given index, or at top
953 953 * of notebook if index smaller than 0.
954 954 *
955 955 * default index value is the one of currently selected cell
956 956 *
957 957 * @param [type] {string} cell type
958 958 * @param [index] {integer}
959 959 *
960 960 * @return handle to created cell or null
961 961 **/
962 962 Notebook.prototype.insert_cell_above = function (type, index) {
963 963 index = this.index_or_selected(index);
964 964 return this.insert_cell_at_index(type, index);
965 965 };
966 966
967 967 /**
968 968 * Insert a cell of given type below given index, or at bottom
969 969 * of notebook if index greater than number of cells
970 970 *
971 971 * default index value is the one of currently selected cell
972 972 *
973 973 * @param [type] {string} cell type
974 974 * @param [index] {integer}
975 975 *
976 976 * @return handle to created cell or null
977 977 **/
978 978 Notebook.prototype.insert_cell_below = function (type, index) {
979 979 index = this.index_or_selected(index);
980 980 return this.insert_cell_at_index(type, index+1);
981 981 };
982 982
983 983
984 984 /**
985 985 * Insert cell at end of notebook
986 986 *
987 987 * @param {String} type cell type
988 988 *
989 989 * @return the added cell; or null
990 990 **/
991 991 Notebook.prototype.insert_cell_at_bottom = function (type){
992 992 var len = this.ncells();
993 993 return this.insert_cell_below(type,len-1);
994 994 };
995 995
996 996 /**
997 997 * Turn a cell into a code cell.
998 998 *
999 999 * @param {Number} [index] A cell's index
1000 1000 */
1001 1001 Notebook.prototype.to_code = function (index) {
1002 1002 var i = this.index_or_selected(index);
1003 1003 if (this.is_valid_cell_index(i)) {
1004 1004 var source_cell = this.get_cell(i);
1005 1005 if (!(source_cell instanceof codecell.CodeCell)) {
1006 1006 var target_cell = this.insert_cell_below('code',i);
1007 1007 var text = source_cell.get_text();
1008 1008 if (text === source_cell.placeholder) {
1009 1009 text = '';
1010 1010 }
1011 1011 //metadata
1012 1012 target_cell.metadata = source_cell.metadata;
1013 1013
1014 1014 target_cell.set_text(text);
1015 1015 // make this value the starting point, so that we can only undo
1016 1016 // to this state, instead of a blank cell
1017 1017 target_cell.code_mirror.clearHistory();
1018 1018 source_cell.element.remove();
1019 1019 this.select(i);
1020 1020 var cursor = source_cell.code_mirror.getCursor();
1021 1021 target_cell.code_mirror.setCursor(cursor);
1022 1022 this.set_dirty(true);
1023 1023 }
1024 1024 }
1025 1025 };
1026 1026
1027 1027 /**
1028 1028 * Turn a cell into a Markdown cell.
1029 1029 *
1030 1030 * @param {Number} [index] A cell's index
1031 1031 */
1032 1032 Notebook.prototype.to_markdown = function (index) {
1033 1033 var i = this.index_or_selected(index);
1034 1034 if (this.is_valid_cell_index(i)) {
1035 1035 var source_cell = this.get_cell(i);
1036 1036
1037 1037 if (!(source_cell instanceof textcell.MarkdownCell)) {
1038 1038 var target_cell = this.insert_cell_below('markdown',i);
1039 1039 var text = source_cell.get_text();
1040 1040
1041 1041 if (text === source_cell.placeholder) {
1042 1042 text = '';
1043 1043 }
1044 1044 // metadata
1045 1045 target_cell.metadata = source_cell.metadata;
1046 1046 // We must show the editor before setting its contents
1047 1047 target_cell.unrender();
1048 1048 target_cell.set_text(text);
1049 1049 // make this value the starting point, so that we can only undo
1050 1050 // to this state, instead of a blank cell
1051 1051 target_cell.code_mirror.clearHistory();
1052 1052 source_cell.element.remove();
1053 1053 this.select(i);
1054 1054 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1055 1055 target_cell.render();
1056 1056 }
1057 1057 var cursor = source_cell.code_mirror.getCursor();
1058 1058 target_cell.code_mirror.setCursor(cursor);
1059 1059 this.set_dirty(true);
1060 1060 }
1061 1061 }
1062 1062 };
1063 1063
1064 1064 /**
1065 1065 * Turn a cell into a raw text cell.
1066 1066 *
1067 1067 * @param {Number} [index] A cell's index
1068 1068 */
1069 1069 Notebook.prototype.to_raw = function (index) {
1070 1070 var i = this.index_or_selected(index);
1071 1071 if (this.is_valid_cell_index(i)) {
1072 1072 var target_cell = null;
1073 1073 var source_cell = this.get_cell(i);
1074 1074
1075 1075 if (!(source_cell instanceof textcell.RawCell)) {
1076 1076 target_cell = this.insert_cell_below('raw',i);
1077 1077 var text = source_cell.get_text();
1078 1078 if (text === source_cell.placeholder) {
1079 1079 text = '';
1080 1080 }
1081 1081 //metadata
1082 1082 target_cell.metadata = source_cell.metadata;
1083 1083 // We must show the editor before setting its contents
1084 1084 target_cell.unrender();
1085 1085 target_cell.set_text(text);
1086 1086 // make this value the starting point, so that we can only undo
1087 1087 // to this state, instead of a blank cell
1088 1088 target_cell.code_mirror.clearHistory();
1089 1089 source_cell.element.remove();
1090 1090 this.select(i);
1091 1091 var cursor = source_cell.code_mirror.getCursor();
1092 1092 target_cell.code_mirror.setCursor(cursor);
1093 1093 this.set_dirty(true);
1094 1094 }
1095 1095 }
1096 1096 };
1097 1097
1098 1098 Notebook.prototype._warn_heading = function () {
1099 1099 /**
1100 * warn about heading cells being removed
1100 1101 * @private
1101 1102 */
1102 // warn about heading cells being removed
1103 1103 dialog.modal({
1104 1104 notebook: this,
1105 1105 keyboard_manager: this.keyboard_manager,
1106 1106 title : "Use markdown headings",
1107 1107 body : $("<p/>").text(
1108 1108 'IPython no longer uses special heading cells. ' +
1109 1109 'Instead, write your headings in Markdown cells using # characters:'
1110 1110 ).append($('<pre/>').text(
1111 1111 '## This is a level 2 heading'
1112 1112 )),
1113 1113 buttons : {
1114 1114 "OK" : {}
1115 1115 }
1116 1116 });
1117 1117 };
1118 1118
1119 1119 Notebook.prototype.to_heading = function (index, level) {
1120 1120 /**
1121 1121 * Turn a cell into a markdown cell with a heading.
1122 1122 *
1123 1123 * @param {Number} [index] A cell's index
1124 1124 * @param {Number} [level] A heading level (e.g., 1 for h1)
1125 1125 */
1126 1126 this.to_markdown(index);
1127 1127 level = level || 1;
1128 1128 var i = this.index_or_selected(index);
1129 1129 if (this.is_valid_cell_index(i)) {
1130 1130 var cell = this.get_cell(i);
1131 1131 cell.set_heading_level(level);
1132 1132 this.set_dirty(true);
1133 1133 }
1134 1134 };
1135 1135
1136 1136
1137 1137 // Cut/Copy/Paste
1138 1138
1139 1139 /**
1140 1140 * Enable UI elements for pasting cells.
1141 1141 */
1142 1142 Notebook.prototype.enable_paste = function () {
1143 1143 var that = this;
1144 1144 if (!this.paste_enabled) {
1145 1145 $('#paste_cell_replace').removeClass('disabled')
1146 1146 .on('click', function () {that.paste_cell_replace();});
1147 1147 $('#paste_cell_above').removeClass('disabled')
1148 1148 .on('click', function () {that.paste_cell_above();});
1149 1149 $('#paste_cell_below').removeClass('disabled')
1150 1150 .on('click', function () {that.paste_cell_below();});
1151 1151 this.paste_enabled = true;
1152 1152 }
1153 1153 };
1154 1154
1155 1155 /**
1156 1156 * Disable UI elements for pasting cells.
1157 1157 */
1158 1158 Notebook.prototype.disable_paste = function () {
1159 1159 if (this.paste_enabled) {
1160 1160 $('#paste_cell_replace').addClass('disabled').off('click');
1161 1161 $('#paste_cell_above').addClass('disabled').off('click');
1162 1162 $('#paste_cell_below').addClass('disabled').off('click');
1163 1163 this.paste_enabled = false;
1164 1164 }
1165 1165 };
1166 1166
1167 1167 /**
1168 1168 * Cut a cell.
1169 1169 */
1170 1170 Notebook.prototype.cut_cell = function () {
1171 1171 this.copy_cell();
1172 1172 this.delete_cell();
1173 1173 };
1174 1174
1175 1175 /**
1176 1176 * Copy a cell.
1177 1177 */
1178 1178 Notebook.prototype.copy_cell = function () {
1179 1179 var cell = this.get_selected_cell();
1180 1180 this.clipboard = cell.toJSON();
1181 1181 // remove undeletable status from the copied cell
1182 1182 if (this.clipboard.metadata.deletable !== undefined) {
1183 1183 delete this.clipboard.metadata.deletable;
1184 1184 }
1185 1185 this.enable_paste();
1186 1186 };
1187 1187
1188 1188 /**
1189 1189 * Replace the selected cell with a cell in the clipboard.
1190 1190 */
1191 1191 Notebook.prototype.paste_cell_replace = function () {
1192 1192 if (this.clipboard !== null && this.paste_enabled) {
1193 1193 var cell_data = this.clipboard;
1194 1194 var new_cell = this.insert_cell_above(cell_data.cell_type);
1195 1195 new_cell.fromJSON(cell_data);
1196 1196 var old_cell = this.get_next_cell(new_cell);
1197 1197 this.delete_cell(this.find_cell_index(old_cell));
1198 1198 this.select(this.find_cell_index(new_cell));
1199 1199 }
1200 1200 };
1201 1201
1202 1202 /**
1203 1203 * Paste a cell from the clipboard above the selected cell.
1204 1204 */
1205 1205 Notebook.prototype.paste_cell_above = function () {
1206 1206 if (this.clipboard !== null && this.paste_enabled) {
1207 1207 var cell_data = this.clipboard;
1208 1208 var new_cell = this.insert_cell_above(cell_data.cell_type);
1209 1209 new_cell.fromJSON(cell_data);
1210 1210 new_cell.focus_cell();
1211 1211 }
1212 1212 };
1213 1213
1214 1214 /**
1215 1215 * Paste a cell from the clipboard below the selected cell.
1216 1216 */
1217 1217 Notebook.prototype.paste_cell_below = function () {
1218 1218 if (this.clipboard !== null && this.paste_enabled) {
1219 1219 var cell_data = this.clipboard;
1220 1220 var new_cell = this.insert_cell_below(cell_data.cell_type);
1221 1221 new_cell.fromJSON(cell_data);
1222 1222 new_cell.focus_cell();
1223 1223 }
1224 1224 };
1225 1225
1226 1226 // Split/merge
1227 1227
1228 1228 /**
1229 1229 * Split the selected cell into two, at the cursor.
1230 1230 */
1231 1231 Notebook.prototype.split_cell = function () {
1232 1232 var cell = this.get_selected_cell();
1233 1233 if (cell.is_splittable()) {
1234 1234 var texta = cell.get_pre_cursor();
1235 1235 var textb = cell.get_post_cursor();
1236 1236 cell.set_text(textb);
1237 1237 var new_cell = this.insert_cell_above(cell.cell_type);
1238 1238 // Unrender the new cell so we can call set_text.
1239 1239 new_cell.unrender();
1240 1240 new_cell.set_text(texta);
1241 1241 }
1242 1242 };
1243 1243
1244 1244 /**
1245 1245 * Combine the selected cell into the cell above it.
1246 1246 */
1247 1247 Notebook.prototype.merge_cell_above = function () {
1248 1248 var index = this.get_selected_index();
1249 1249 var cell = this.get_cell(index);
1250 1250 var render = cell.rendered;
1251 1251 if (!cell.is_mergeable()) {
1252 1252 return;
1253 1253 }
1254 1254 if (index > 0) {
1255 1255 var upper_cell = this.get_cell(index-1);
1256 1256 if (!upper_cell.is_mergeable()) {
1257 1257 return;
1258 1258 }
1259 1259 var upper_text = upper_cell.get_text();
1260 1260 var text = cell.get_text();
1261 1261 if (cell instanceof codecell.CodeCell) {
1262 1262 cell.set_text(upper_text+'\n'+text);
1263 1263 } else {
1264 1264 cell.unrender(); // Must unrender before we set_text.
1265 1265 cell.set_text(upper_text+'\n\n'+text);
1266 1266 if (render) {
1267 1267 // The rendered state of the final cell should match
1268 1268 // that of the original selected cell;
1269 1269 cell.render();
1270 1270 }
1271 1271 }
1272 1272 this.delete_cell(index-1);
1273 1273 this.select(this.find_cell_index(cell));
1274 1274 }
1275 1275 };
1276 1276
1277 1277 /**
1278 1278 * Combine the selected cell into the cell below it.
1279 1279 */
1280 1280 Notebook.prototype.merge_cell_below = function () {
1281 1281 var index = this.get_selected_index();
1282 1282 var cell = this.get_cell(index);
1283 1283 var render = cell.rendered;
1284 1284 if (!cell.is_mergeable()) {
1285 1285 return;
1286 1286 }
1287 1287 if (index < this.ncells()-1) {
1288 1288 var lower_cell = this.get_cell(index+1);
1289 1289 if (!lower_cell.is_mergeable()) {
1290 1290 return;
1291 1291 }
1292 1292 var lower_text = lower_cell.get_text();
1293 1293 var text = cell.get_text();
1294 1294 if (cell instanceof codecell.CodeCell) {
1295 1295 cell.set_text(text+'\n'+lower_text);
1296 1296 } else {
1297 1297 cell.unrender(); // Must unrender before we set_text.
1298 1298 cell.set_text(text+'\n\n'+lower_text);
1299 1299 if (render) {
1300 1300 // The rendered state of the final cell should match
1301 1301 // that of the original selected cell;
1302 1302 cell.render();
1303 1303 }
1304 1304 }
1305 1305 this.delete_cell(index+1);
1306 1306 this.select(this.find_cell_index(cell));
1307 1307 }
1308 1308 };
1309 1309
1310 1310
1311 1311 // Cell collapsing and output clearing
1312 1312
1313 1313 /**
1314 1314 * Hide a cell's output.
1315 1315 *
1316 1316 * @param {Number} index A cell's numeric index
1317 1317 */
1318 1318 Notebook.prototype.collapse_output = function (index) {
1319 1319 var i = this.index_or_selected(index);
1320 1320 var cell = this.get_cell(i);
1321 1321 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1322 1322 cell.collapse_output();
1323 1323 this.set_dirty(true);
1324 1324 }
1325 1325 };
1326 1326
1327 1327 /**
1328 1328 * Hide each code cell's output area.
1329 1329 */
1330 1330 Notebook.prototype.collapse_all_output = function () {
1331 1331 this.get_cells().map(function (cell, i) {
1332 1332 if (cell instanceof codecell.CodeCell) {
1333 1333 cell.collapse_output();
1334 1334 }
1335 1335 });
1336 1336 // this should not be set if the `collapse` key is removed from nbformat
1337 1337 this.set_dirty(true);
1338 1338 };
1339 1339
1340 1340 /**
1341 1341 * Show a cell's output.
1342 1342 *
1343 1343 * @param {Number} index A cell's numeric index
1344 1344 */
1345 1345 Notebook.prototype.expand_output = function (index) {
1346 1346 var i = this.index_or_selected(index);
1347 1347 var cell = this.get_cell(i);
1348 1348 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1349 1349 cell.expand_output();
1350 1350 this.set_dirty(true);
1351 1351 }
1352 1352 };
1353 1353
1354 1354 /**
1355 1355 * Expand each code cell's output area, and remove scrollbars.
1356 1356 */
1357 1357 Notebook.prototype.expand_all_output = function () {
1358 1358 this.get_cells().map(function (cell, i) {
1359 1359 if (cell instanceof codecell.CodeCell) {
1360 1360 cell.expand_output();
1361 1361 }
1362 1362 });
1363 1363 // this should not be set if the `collapse` key is removed from nbformat
1364 1364 this.set_dirty(true);
1365 1365 };
1366 1366
1367 1367 /**
1368 1368 * Clear the selected CodeCell's output area.
1369 1369 *
1370 1370 * @param {Number} index A cell's numeric index
1371 1371 */
1372 1372 Notebook.prototype.clear_output = function (index) {
1373 1373 var i = this.index_or_selected(index);
1374 1374 var cell = this.get_cell(i);
1375 1375 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1376 1376 cell.clear_output();
1377 1377 this.set_dirty(true);
1378 1378 }
1379 1379 };
1380 1380
1381 1381 /**
1382 1382 * Clear each code cell's output area.
1383 1383 */
1384 1384 Notebook.prototype.clear_all_output = function () {
1385 1385 this.get_cells().map(function (cell, i) {
1386 1386 if (cell instanceof codecell.CodeCell) {
1387 1387 cell.clear_output();
1388 1388 }
1389 1389 });
1390 1390 this.set_dirty(true);
1391 1391 };
1392 1392
1393 1393 /**
1394 1394 * Scroll the selected CodeCell's output area.
1395 1395 *
1396 1396 * @param {Number} index A cell's numeric index
1397 1397 */
1398 1398 Notebook.prototype.scroll_output = function (index) {
1399 1399 var i = this.index_or_selected(index);
1400 1400 var cell = this.get_cell(i);
1401 1401 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1402 1402 cell.scroll_output();
1403 1403 this.set_dirty(true);
1404 1404 }
1405 1405 };
1406 1406
1407 1407 /**
1408 1408 * Expand each code cell's output area, and add a scrollbar for long output.
1409 1409 */
1410 1410 Notebook.prototype.scroll_all_output = function () {
1411 1411 this.get_cells().map(function (cell, i) {
1412 1412 if (cell instanceof codecell.CodeCell) {
1413 1413 cell.scroll_output();
1414 1414 }
1415 1415 });
1416 1416 // this should not be set if the `collapse` key is removed from nbformat
1417 1417 this.set_dirty(true);
1418 1418 };
1419 1419
1420 1420 /** Toggle whether a cell's output is collapsed or expanded.
1421 1421 *
1422 1422 * @param {Number} index A cell's numeric index
1423 1423 */
1424 1424 Notebook.prototype.toggle_output = function (index) {
1425 1425 var i = this.index_or_selected(index);
1426 1426 var cell = this.get_cell(i);
1427 1427 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1428 1428 cell.toggle_output();
1429 1429 this.set_dirty(true);
1430 1430 }
1431 1431 };
1432 1432
1433 1433 /**
1434 1434 * Hide/show the output of all cells.
1435 1435 */
1436 1436 Notebook.prototype.toggle_all_output = function () {
1437 1437 this.get_cells().map(function (cell, i) {
1438 1438 if (cell instanceof codecell.CodeCell) {
1439 1439 cell.toggle_output();
1440 1440 }
1441 1441 });
1442 1442 // this should not be set if the `collapse` key is removed from nbformat
1443 1443 this.set_dirty(true);
1444 1444 };
1445 1445
1446 1446 /**
1447 1447 * Toggle a scrollbar for long cell outputs.
1448 1448 *
1449 1449 * @param {Number} index A cell's numeric index
1450 1450 */
1451 1451 Notebook.prototype.toggle_output_scroll = function (index) {
1452 1452 var i = this.index_or_selected(index);
1453 1453 var cell = this.get_cell(i);
1454 1454 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1455 1455 cell.toggle_output_scroll();
1456 1456 this.set_dirty(true);
1457 1457 }
1458 1458 };
1459 1459
1460 1460 /**
1461 1461 * Toggle the scrolling of long output on all cells.
1462 1462 */
1463 1463 Notebook.prototype.toggle_all_output_scroll = function () {
1464 1464 this.get_cells().map(function (cell, i) {
1465 1465 if (cell instanceof codecell.CodeCell) {
1466 1466 cell.toggle_output_scroll();
1467 1467 }
1468 1468 });
1469 1469 // this should not be set if the `collapse` key is removed from nbformat
1470 1470 this.set_dirty(true);
1471 1471 };
1472 1472
1473 1473 // Other cell functions: line numbers, ...
1474 1474
1475 1475 /**
1476 1476 * Toggle line numbers in the selected cell's input area.
1477 1477 */
1478 1478 Notebook.prototype.cell_toggle_line_numbers = function() {
1479 1479 this.get_selected_cell().toggle_line_numbers();
1480 1480 };
1481 1481
1482 1482 /**
1483 1483 * Set the codemirror mode for all code cells, including the default for
1484 1484 * new code cells.
1485 1485 */
1486 1486 Notebook.prototype.set_codemirror_mode = function(newmode){
1487 1487 if (newmode === this.codemirror_mode) {
1488 1488 return;
1489 1489 }
1490 1490 this.codemirror_mode = newmode;
1491 1491 codecell.CodeCell.options_default.cm_config.mode = newmode;
1492 1492
1493 1493 var that = this;
1494 1494 utils.requireCodeMirrorMode(newmode, function (spec) {
1495 1495 that.get_cells().map(function(cell, i) {
1496 1496 if (cell.cell_type === 'code'){
1497 1497 cell.code_mirror.setOption('mode', spec);
1498 1498 // This is currently redundant, because cm_config ends up as
1499 1499 // codemirror's own .options object, but I don't want to
1500 1500 // rely on that.
1501 1501 cell.cm_config.mode = spec;
1502 1502 }
1503 1503 });
1504 1504 });
1505 1505 };
1506 1506
1507 1507 // Session related things
1508 1508
1509 1509 /**
1510 1510 * Start a new session and set it on each code cell.
1511 1511 */
1512 1512 Notebook.prototype.start_session = function (kernel_name) {
1513 1513 if (this._session_starting) {
1514 1514 throw new session.SessionAlreadyStarting();
1515 1515 }
1516 1516 this._session_starting = true;
1517 1517
1518 1518 var options = {
1519 1519 base_url: this.base_url,
1520 1520 ws_url: this.ws_url,
1521 1521 notebook_path: this.notebook_path,
1522 1522 notebook_name: this.notebook_name,
1523 1523 kernel_name: kernel_name,
1524 1524 notebook: this
1525 1525 };
1526 1526
1527 1527 var success = $.proxy(this._session_started, this);
1528 1528 var failure = $.proxy(this._session_start_failed, this);
1529 1529
1530 1530 if (this.session !== null) {
1531 1531 this.session.restart(options, success, failure);
1532 1532 } else {
1533 1533 this.session = new session.Session(options);
1534 1534 this.session.start(success, failure);
1535 1535 }
1536 1536 };
1537 1537
1538 1538
1539 1539 /**
1540 1540 * Once a session is started, link the code cells to the kernel and pass the
1541 1541 * comm manager to the widget manager
1542 1542 * @private
1543 1543 */
1544 1544 Notebook.prototype._session_started = function (){
1545 1545 this._session_starting = false;
1546 1546 this.kernel = this.session.kernel;
1547 1547 var ncells = this.ncells();
1548 1548 for (var i=0; i<ncells; i++) {
1549 1549 var cell = this.get_cell(i);
1550 1550 if (cell instanceof codecell.CodeCell) {
1551 1551 cell.set_kernel(this.session.kernel);
1552 1552 }
1553 1553 }
1554 1554 };
1555 1555 /**
1556 1556 * @private
1557 1557 */
1558 1558 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1559 1559 this._session_starting = false;
1560 1560 utils.log_ajax_error(jqxhr, status, error);
1561 1561 };
1562 1562
1563 1563 /**
1564 1564 * Prompt the user to restart the IPython kernel.
1565 1565 */
1566 1566 Notebook.prototype.restart_kernel = function () {
1567 1567 var that = this;
1568 1568 dialog.modal({
1569 1569 notebook: this,
1570 1570 keyboard_manager: this.keyboard_manager,
1571 1571 title : "Restart kernel or continue running?",
1572 1572 body : $("<p/>").text(
1573 1573 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1574 1574 ),
1575 1575 buttons : {
1576 1576 "Continue running" : {},
1577 1577 "Restart" : {
1578 1578 "class" : "btn-danger",
1579 1579 "click" : function() {
1580 1580 that.kernel.restart();
1581 1581 }
1582 1582 }
1583 1583 }
1584 1584 });
1585 1585 };
1586 1586
1587 1587 /**
1588 1588 * Execute or render cell outputs and go into command mode.
1589 1589 */
1590 1590 Notebook.prototype.execute_cell = function () {
1591 1591 // mode = shift, ctrl, alt
1592 1592 var cell = this.get_selected_cell();
1593 1593
1594 1594 cell.execute();
1595 1595 this.command_mode();
1596 1596 this.set_dirty(true);
1597 1597 };
1598 1598
1599 1599 /**
1600 1600 * Execute or render cell outputs and insert a new cell below.
1601 1601 */
1602 1602 Notebook.prototype.execute_cell_and_insert_below = function () {
1603 1603 var cell = this.get_selected_cell();
1604 1604 var cell_index = this.find_cell_index(cell);
1605 1605
1606 1606 cell.execute();
1607 1607
1608 1608 // If we are at the end always insert a new cell and return
1609 1609 if (cell_index === (this.ncells()-1)) {
1610 1610 this.command_mode();
1611 1611 this.insert_cell_below();
1612 1612 this.select(cell_index+1);
1613 1613 this.edit_mode();
1614 1614 this.scroll_to_bottom();
1615 1615 this.set_dirty(true);
1616 1616 return;
1617 1617 }
1618 1618
1619 1619 this.command_mode();
1620 1620 this.insert_cell_below();
1621 1621 this.select(cell_index+1);
1622 1622 this.edit_mode();
1623 1623 this.set_dirty(true);
1624 1624 };
1625 1625
1626 1626 /**
1627 1627 * Execute or render cell outputs and select the next cell.
1628 1628 */
1629 1629 Notebook.prototype.execute_cell_and_select_below = function () {
1630 1630
1631 1631 var cell = this.get_selected_cell();
1632 1632 var cell_index = this.find_cell_index(cell);
1633 1633
1634 1634 cell.execute();
1635 1635
1636 1636 // If we are at the end always insert a new cell and return
1637 1637 if (cell_index === (this.ncells()-1)) {
1638 1638 this.command_mode();
1639 1639 this.insert_cell_below();
1640 1640 this.select(cell_index+1);
1641 1641 this.edit_mode();
1642 1642 this.scroll_to_bottom();
1643 1643 this.set_dirty(true);
1644 1644 return;
1645 1645 }
1646 1646
1647 1647 this.command_mode();
1648 1648 this.select(cell_index+1);
1649 1649 this.focus_cell();
1650 1650 this.set_dirty(true);
1651 1651 };
1652 1652
1653 1653 /**
1654 1654 * Execute all cells below the selected cell.
1655 1655 */
1656 1656 Notebook.prototype.execute_cells_below = function () {
1657 1657 this.execute_cell_range(this.get_selected_index(), this.ncells());
1658 1658 this.scroll_to_bottom();
1659 1659 };
1660 1660
1661 1661 /**
1662 1662 * Execute all cells above the selected cell.
1663 1663 */
1664 1664 Notebook.prototype.execute_cells_above = function () {
1665 1665 this.execute_cell_range(0, this.get_selected_index());
1666 1666 };
1667 1667
1668 1668 /**
1669 1669 * Execute all cells.
1670 1670 */
1671 1671 Notebook.prototype.execute_all_cells = function () {
1672 1672 this.execute_cell_range(0, this.ncells());
1673 1673 this.scroll_to_bottom();
1674 1674 };
1675 1675
1676 1676 /**
1677 1677 * Execute a contiguous range of cells.
1678 1678 *
1679 1679 * @param {Number} start Index of the first cell to execute (inclusive)
1680 1680 * @param {Number} end Index of the last cell to execute (exclusive)
1681 1681 */
1682 1682 Notebook.prototype.execute_cell_range = function (start, end) {
1683 1683 this.command_mode();
1684 1684 for (var i=start; i<end; i++) {
1685 1685 this.select(i);
1686 1686 this.execute_cell();
1687 1687 }
1688 1688 };
1689 1689
1690 1690 // Persistance and loading
1691 1691
1692 1692 /**
1693 1693 * Getter method for this notebook's name.
1694 1694 *
1695 1695 * @return {String} This notebook's name (excluding file extension)
1696 1696 */
1697 1697 Notebook.prototype.get_notebook_name = function () {
1698 1698 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1699 1699 return nbname;
1700 1700 };
1701 1701
1702 1702 /**
1703 1703 * Setter method for this notebook's name.
1704 1704 *
1705 1705 * @param {String} name A new name for this notebook
1706 1706 */
1707 1707 Notebook.prototype.set_notebook_name = function (name) {
1708 1708 var parent = utils.url_path_split(this.notebook_path)[0];
1709 1709 this.notebook_name = name;
1710 1710 this.notebook_path = utils.url_path_join(parent, name);
1711 1711 };
1712 1712
1713 1713 /**
1714 1714 * Check that a notebook's name is valid.
1715 1715 *
1716 1716 * @param {String} nbname A name for this notebook
1717 1717 * @return {Boolean} True if the name is valid, false if invalid
1718 1718 */
1719 1719 Notebook.prototype.test_notebook_name = function (nbname) {
1720 1720 nbname = nbname || '';
1721 1721 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1722 1722 return true;
1723 1723 } else {
1724 1724 return false;
1725 1725 }
1726 1726 };
1727 1727
1728 1728 /**
1729 1729 * Load a notebook from JSON (.ipynb).
1730 1730 *
1731 1731 * @param {Object} data JSON representation of a notebook
1732 1732 */
1733 1733 Notebook.prototype.fromJSON = function (data) {
1734 1734
1735 1735 var content = data.content;
1736 1736 var ncells = this.ncells();
1737 1737 var i;
1738 1738 for (i=0; i<ncells; i++) {
1739 1739 // Always delete cell 0 as they get renumbered as they are deleted.
1740 1740 this.delete_cell(0);
1741 1741 }
1742 1742 // Save the metadata and name.
1743 1743 this.metadata = content.metadata;
1744 1744 this.notebook_name = data.name;
1745 1745 this.notebook_path = data.path;
1746 1746 var trusted = true;
1747 1747
1748 1748 // Trigger an event changing the kernel spec - this will set the default
1749 1749 // codemirror mode
1750 1750 if (this.metadata.kernelspec !== undefined) {
1751 1751 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1752 1752 }
1753 1753
1754 1754 // Set the codemirror mode from language_info metadata
1755 1755 if (this.metadata.language_info !== undefined) {
1756 1756 var langinfo = this.metadata.language_info;
1757 1757 // Mode 'null' should be plain, unhighlighted text.
1758 1758 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1759 1759 this.set_codemirror_mode(cm_mode);
1760 1760 }
1761 1761
1762 1762 var new_cells = content.cells;
1763 1763 ncells = new_cells.length;
1764 1764 var cell_data = null;
1765 1765 var new_cell = null;
1766 1766 for (i=0; i<ncells; i++) {
1767 1767 cell_data = new_cells[i];
1768 1768 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1769 1769 new_cell.fromJSON(cell_data);
1770 1770 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1771 1771 trusted = false;
1772 1772 }
1773 1773 }
1774 1774 if (trusted !== this.trusted) {
1775 1775 this.trusted = trusted;
1776 1776 this.events.trigger("trust_changed.Notebook", trusted);
1777 1777 }
1778 1778 };
1779 1779
1780 1780 /**
1781 1781 * Dump this notebook into a JSON-friendly object.
1782 1782 *
1783 1783 * @return {Object} A JSON-friendly representation of this notebook.
1784 1784 */
1785 1785 Notebook.prototype.toJSON = function () {
1786 1786 // remove the conversion indicator, which only belongs in-memory
1787 1787 delete this.metadata.orig_nbformat;
1788 1788 delete this.metadata.orig_nbformat_minor;
1789 1789
1790 1790 var cells = this.get_cells();
1791 1791 var ncells = cells.length;
1792 1792 var cell_array = new Array(ncells);
1793 1793 var trusted = true;
1794 1794 for (var i=0; i<ncells; i++) {
1795 1795 var cell = cells[i];
1796 1796 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1797 1797 trusted = false;
1798 1798 }
1799 1799 cell_array[i] = cell.toJSON();
1800 1800 }
1801 1801 var data = {
1802 1802 cells: cell_array,
1803 1803 metadata: this.metadata,
1804 1804 nbformat: this.nbformat,
1805 1805 nbformat_minor: this.nbformat_minor
1806 1806 };
1807 1807 if (trusted != this.trusted) {
1808 1808 this.trusted = trusted;
1809 1809 this.events.trigger("trust_changed.Notebook", trusted);
1810 1810 }
1811 1811 return data;
1812 1812 };
1813 1813
1814 1814 /**
1815 1815 * Start an autosave timer, for periodically saving the notebook.
1816 1816 *
1817 1817 * @param {Integer} interval the autosave interval in milliseconds
1818 1818 */
1819 1819 Notebook.prototype.set_autosave_interval = function (interval) {
1820 1820 var that = this;
1821 1821 // clear previous interval, so we don't get simultaneous timers
1822 1822 if (this.autosave_timer) {
1823 1823 clearInterval(this.autosave_timer);
1824 1824 }
1825 1825 if (!this.writable) {
1826 1826 // disable autosave if not writable
1827 1827 interval = 0;
1828 1828 }
1829 1829
1830 1830 this.autosave_interval = this.minimum_autosave_interval = interval;
1831 1831 if (interval) {
1832 1832 this.autosave_timer = setInterval(function() {
1833 1833 if (that.dirty) {
1834 1834 that.save_notebook();
1835 1835 }
1836 1836 }, interval);
1837 1837 this.events.trigger("autosave_enabled.Notebook", interval);
1838 1838 } else {
1839 1839 this.autosave_timer = null;
1840 1840 this.events.trigger("autosave_disabled.Notebook");
1841 1841 }
1842 1842 };
1843 1843
1844 1844 /**
1845 1845 * Save this notebook on the server. This becomes a notebook instance's
1846 1846 * .save_notebook method *after* the entire notebook has been loaded.
1847 1847 */
1848 1848 Notebook.prototype.save_notebook = function () {
1849 1849 if (!this._fully_loaded) {
1850 1850 this.events.trigger('notebook_save_failed.Notebook',
1851 1851 new Error("Load failed, save is disabled")
1852 1852 );
1853 1853 return;
1854 1854 } else if (!this.writable) {
1855 1855 this.events.trigger('notebook_save_failed.Notebook',
1856 1856 new Error("Notebook is read-only")
1857 1857 );
1858 1858 return;
1859 1859 }
1860 1860
1861 1861 // Trigger an event before save, which allows listeners to modify
1862 1862 // the notebook as needed.
1863 1863 this.events.trigger('before_save.Notebook');
1864 1864
1865 1865 // Create a JSON model to be sent to the server.
1866 1866 var model = {
1867 1867 type : "notebook",
1868 1868 content : this.toJSON()
1869 1869 };
1870 1870 // time the ajax call for autosave tuning purposes.
1871 1871 var start = new Date().getTime();
1872 1872
1873 1873 var that = this;
1874 1874 return this.contents.save(this.notebook_path, model).then(
1875 1875 $.proxy(this.save_notebook_success, this, start),
1876 1876 function (error) {
1877 1877 that.events.trigger('notebook_save_failed.Notebook', error);
1878 1878 }
1879 1879 );
1880 1880 };
1881 1881
1882 1882 /**
1883 1883 * Success callback for saving a notebook.
1884 1884 *
1885 1885 * @param {Integer} start Time when the save request start
1886 1886 * @param {Object} data JSON representation of a notebook
1887 1887 */
1888 1888 Notebook.prototype.save_notebook_success = function (start, data) {
1889 1889 this.set_dirty(false);
1890 1890 if (data.message) {
1891 1891 // save succeeded, but validation failed.
1892 1892 var body = $("<div>");
1893 1893 var title = "Notebook validation failed";
1894 1894
1895 1895 body.append($("<p>").text(
1896 1896 "The save operation succeeded," +
1897 1897 " but the notebook does not appear to be valid." +
1898 1898 " The validation error was:"
1899 1899 )).append($("<div>").addClass("validation-error").append(
1900 1900 $("<pre>").text(data.message)
1901 1901 ));
1902 1902 dialog.modal({
1903 1903 notebook: this,
1904 1904 keyboard_manager: this.keyboard_manager,
1905 1905 title: title,
1906 1906 body: body,
1907 1907 buttons : {
1908 1908 OK : {
1909 1909 "class" : "btn-primary"
1910 1910 }
1911 1911 }
1912 1912 });
1913 1913 }
1914 1914 this.events.trigger('notebook_saved.Notebook');
1915 1915 this._update_autosave_interval(start);
1916 1916 if (this._checkpoint_after_save) {
1917 1917 this.create_checkpoint();
1918 1918 this._checkpoint_after_save = false;
1919 1919 }
1920 1920 };
1921 1921
1922 1922 /**
1923 1923 * update the autosave interval based on how long the last save took
1924 1924 *
1925 1925 * @param {Integer} timestamp when the save request started
1926 1926 * @private
1927 1927 */
1928 1928 Notebook.prototype._update_autosave_interval = function (start) {
1929 1929 var duration = (new Date().getTime() - start);
1930 1930 if (this.autosave_interval) {
1931 1931 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1932 1932 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1933 1933 // round to 10 seconds, otherwise we will be setting a new interval too often
1934 1934 interval = 10000 * Math.round(interval / 10000);
1935 1935 // set new interval, if it's changed
1936 1936 if (interval != this.autosave_interval) {
1937 1937 this.set_autosave_interval(interval);
1938 1938 }
1939 1939 }
1940 1940 };
1941 1941
1942 1942 /**
1943 1943 * Explicitly trust the output of this notebook.
1944 1944 *
1945 1945 */
1946 1946 Notebook.prototype.trust_notebook = function () {
1947 1947 var body = $("<div>").append($("<p>")
1948 1948 .text("A trusted IPython notebook may execute hidden malicious code ")
1949 1949 .append($("<strong>")
1950 1950 .append(
1951 1951 $("<em>").text("when you open it")
1952 1952 )
1953 1953 ).append(".").append(
1954 1954 " Selecting trust will immediately reload this notebook in a trusted state."
1955 1955 ).append(
1956 1956 " For more information, see the "
1957 1957 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
1958 1958 .text("IPython security documentation")
1959 1959 ).append(".")
1960 1960 );
1961 1961
1962 1962 var nb = this;
1963 1963 dialog.modal({
1964 1964 notebook: this,
1965 1965 keyboard_manager: this.keyboard_manager,
1966 1966 title: "Trust this notebook?",
1967 1967 body: body,
1968 1968
1969 1969 buttons: {
1970 1970 Cancel : {},
1971 1971 Trust : {
1972 1972 class : "btn-danger",
1973 1973 click : function () {
1974 1974 var cells = nb.get_cells();
1975 1975 for (var i = 0; i < cells.length; i++) {
1976 1976 var cell = cells[i];
1977 1977 if (cell.cell_type == 'code') {
1978 1978 cell.output_area.trusted = true;
1979 1979 }
1980 1980 }
1981 1981 nb.events.on('notebook_saved.Notebook', function () {
1982 1982 window.location.reload();
1983 1983 });
1984 1984 nb.save_notebook();
1985 1985 }
1986 1986 }
1987 1987 }
1988 1988 });
1989 1989 };
1990 1990
1991 1991 Notebook.prototype.copy_notebook = function () {
1992 1992 var that = this;
1993 1993 var base_url = this.base_url;
1994 1994 var w = window.open();
1995 1995 var parent = utils.url_path_split(this.notebook_path)[0];
1996 1996 this.contents.copy(this.notebook_path, parent).then(
1997 1997 function (data) {
1998 1998 w.location = utils.url_join_encode(
1999 1999 base_url, 'notebooks', data.path
2000 2000 );
2001 2001 },
2002 2002 function(error) {
2003 2003 w.close();
2004 2004 that.events.trigger('notebook_copy_failed', error);
2005 2005 }
2006 2006 );
2007 2007 };
2008 2008
2009 2009 Notebook.prototype.rename = function (new_name) {
2010 2010 if (!new_name.match(/\.ipynb$/)) {
2011 2011 new_name = new_name + ".ipynb";
2012 2012 }
2013 2013
2014 2014 var that = this;
2015 2015 var parent = utils.url_path_split(this.notebook_path)[0];
2016 2016 var new_path = utils.url_path_join(parent, new_name);
2017 2017 return this.contents.rename(this.notebook_path, new_path).then(
2018 2018 function (json) {
2019 2019 that.notebook_name = json.name;
2020 2020 that.notebook_path = json.path;
2021 2021 that.session.rename_notebook(json.path);
2022 2022 that.events.trigger('notebook_renamed.Notebook', json);
2023 2023 }
2024 2024 );
2025 2025 };
2026 2026
2027 2027 Notebook.prototype.delete = function () {
2028 2028 this.contents.delete(this.notebook_path);
2029 2029 };
2030 2030
2031 2031 /**
2032 2032 * Request a notebook's data from the server.
2033 2033 *
2034 2034 * @param {String} notebook_path A notebook to load
2035 2035 */
2036 2036 Notebook.prototype.load_notebook = function (notebook_path) {
2037 2037 this.notebook_path = notebook_path;
2038 2038 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2039 2039 this.events.trigger('notebook_loading.Notebook');
2040 2040 this.contents.get(notebook_path, {type: 'notebook'}).then(
2041 2041 $.proxy(this.load_notebook_success, this),
2042 2042 $.proxy(this.load_notebook_error, this)
2043 2043 );
2044 2044 };
2045 2045
2046 2046 /**
2047 2047 * Success callback for loading a notebook from the server.
2048 2048 *
2049 2049 * Load notebook data from the JSON response.
2050 2050 *
2051 2051 * @param {Object} data JSON representation of a notebook
2052 2052 */
2053 2053 Notebook.prototype.load_notebook_success = function (data) {
2054 2054 var failed, msg;
2055 2055 try {
2056 2056 this.fromJSON(data);
2057 2057 } catch (e) {
2058 2058 failed = e;
2059 2059 console.log("Notebook failed to load from JSON:", e);
2060 2060 }
2061 2061 if (failed || data.message) {
2062 2062 // *either* fromJSON failed or validation failed
2063 2063 var body = $("<div>");
2064 2064 var title;
2065 2065 if (failed) {
2066 2066 title = "Notebook failed to load";
2067 2067 body.append($("<p>").text(
2068 2068 "The error was: "
2069 2069 )).append($("<div>").addClass("js-error").text(
2070 2070 failed.toString()
2071 2071 )).append($("<p>").text(
2072 2072 "See the error console for details."
2073 2073 ));
2074 2074 } else {
2075 2075 title = "Notebook validation failed";
2076 2076 }
2077 2077
2078 2078 if (data.message) {
2079 2079 if (failed) {
2080 2080 msg = "The notebook also failed validation:";
2081 2081 } else {
2082 2082 msg = "An invalid notebook may not function properly." +
2083 2083 " The validation error was:";
2084 2084 }
2085 2085 body.append($("<p>").text(
2086 2086 msg
2087 2087 )).append($("<div>").addClass("validation-error").append(
2088 2088 $("<pre>").text(data.message)
2089 2089 ));
2090 2090 }
2091 2091
2092 2092 dialog.modal({
2093 2093 notebook: this,
2094 2094 keyboard_manager: this.keyboard_manager,
2095 2095 title: title,
2096 2096 body: body,
2097 2097 buttons : {
2098 2098 OK : {
2099 2099 "class" : "btn-primary"
2100 2100 }
2101 2101 }
2102 2102 });
2103 2103 }
2104 2104 if (this.ncells() === 0) {
2105 2105 this.insert_cell_below('code');
2106 2106 this.edit_mode(0);
2107 2107 } else {
2108 2108 this.select(0);
2109 2109 this.handle_command_mode(this.get_cell(0));
2110 2110 }
2111 2111 this.set_dirty(false);
2112 2112 this.scroll_to_top();
2113 2113 this.writable = data.writable || false;
2114 2114 var nbmodel = data.content;
2115 2115 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2116 2116 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2117 2117 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2118 2118 var src;
2119 2119 if (nbmodel.nbformat > orig_nbformat) {
2120 2120 src = " an older notebook format ";
2121 2121 } else {
2122 2122 src = " a newer notebook format ";
2123 2123 }
2124 2124
2125 2125 msg = "This notebook has been converted from" + src +
2126 2126 "(v"+orig_nbformat+") to the current notebook " +
2127 2127 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2128 2128 "current notebook format will be used.";
2129 2129
2130 2130 if (nbmodel.nbformat > orig_nbformat) {
2131 2131 msg += " Older versions of IPython may not be able to read the new format.";
2132 2132 } else {
2133 2133 msg += " Some features of the original notebook may not be available.";
2134 2134 }
2135 2135 msg += " To preserve the original version, close the " +
2136 2136 "notebook without saving it.";
2137 2137 dialog.modal({
2138 2138 notebook: this,
2139 2139 keyboard_manager: this.keyboard_manager,
2140 2140 title : "Notebook converted",
2141 2141 body : msg,
2142 2142 buttons : {
2143 2143 OK : {
2144 2144 class : "btn-primary"
2145 2145 }
2146 2146 }
2147 2147 });
2148 2148 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2149 2149 this.nbformat_minor = nbmodel.nbformat_minor;
2150 2150 }
2151 2151
2152 2152 // Create the session after the notebook is completely loaded to prevent
2153 2153 // code execution upon loading, which is a security risk.
2154 2154 if (this.session === null) {
2155 2155 var kernel_name;
2156 2156 if (this.metadata.kernelspec) {
2157 2157 var kernelspec = this.metadata.kernelspec || {};
2158 2158 kernel_name = kernelspec.name;
2159 2159 } else {
2160 2160 kernel_name = utils.get_url_param('kernel_name');
2161 2161 }
2162 2162 this.start_session(kernel_name);
2163 2163 }
2164 2164 // load our checkpoint list
2165 2165 this.list_checkpoints();
2166 2166
2167 2167 // load toolbar state
2168 2168 if (this.metadata.celltoolbar) {
2169 2169 celltoolbar.CellToolbar.global_show();
2170 2170 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2171 2171 } else {
2172 2172 celltoolbar.CellToolbar.global_hide();
2173 2173 }
2174 2174
2175 2175 if (!this.writable) {
2176 2176 this.set_autosave_interval(0);
2177 2177 this.events.trigger('notebook_read_only.Notebook');
2178 2178 }
2179 2179
2180 2180 // now that we're fully loaded, it is safe to restore save functionality
2181 2181 this._fully_loaded = true;
2182 2182 this.events.trigger('notebook_loaded.Notebook');
2183 2183 };
2184 2184
2185 2185 /**
2186 2186 * Failure callback for loading a notebook from the server.
2187 2187 *
2188 2188 * @param {Error} error
2189 2189 */
2190 2190 Notebook.prototype.load_notebook_error = function (error) {
2191 2191 this.events.trigger('notebook_load_failed.Notebook', error);
2192 2192 var msg;
2193 2193 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2194 2194 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2195 2195 msg = "An unknown error occurred while loading this notebook. " +
2196 2196 "This version can load notebook formats " +
2197 2197 "v" + this.nbformat + " or earlier. See the server log for details.";
2198 2198 } else {
2199 2199 msg = error.message;
2200 2200 }
2201 2201 dialog.modal({
2202 2202 notebook: this,
2203 2203 keyboard_manager: this.keyboard_manager,
2204 2204 title: "Error loading notebook",
2205 2205 body : msg,
2206 2206 buttons : {
2207 2207 "OK": {}
2208 2208 }
2209 2209 });
2210 2210 };
2211 2211
2212 2212 /********************* checkpoint-related *********************/
2213 2213
2214 2214 /**
2215 2215 * Save the notebook then immediately create a checkpoint.
2216 2216 */
2217 2217 Notebook.prototype.save_checkpoint = function () {
2218 2218 this._checkpoint_after_save = true;
2219 2219 this.save_notebook();
2220 2220 };
2221 2221
2222 2222 /**
2223 2223 * Add a checkpoint for this notebook.
2224 2224 * for use as a callback from checkpoint creation.
2225 2225 */
2226 2226 Notebook.prototype.add_checkpoint = function (checkpoint) {
2227 2227 var found = false;
2228 2228 for (var i = 0; i < this.checkpoints.length; i++) {
2229 2229 var existing = this.checkpoints[i];
2230 2230 if (existing.id == checkpoint.id) {
2231 2231 found = true;
2232 2232 this.checkpoints[i] = checkpoint;
2233 2233 break;
2234 2234 }
2235 2235 }
2236 2236 if (!found) {
2237 2237 this.checkpoints.push(checkpoint);
2238 2238 }
2239 2239 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2240 2240 };
2241 2241
2242 2242 /**
2243 2243 * List checkpoints for this notebook.
2244 2244 */
2245 2245 Notebook.prototype.list_checkpoints = function () {
2246 2246 var that = this;
2247 2247 this.contents.list_checkpoints(this.notebook_path).then(
2248 2248 $.proxy(this.list_checkpoints_success, this),
2249 2249 function(error) {
2250 2250 that.events.trigger('list_checkpoints_failed.Notebook', error);
2251 2251 }
2252 2252 );
2253 2253 };
2254 2254
2255 2255 /**
2256 2256 * Success callback for listing checkpoints.
2257 2257 *
2258 2258 * @param {Object} data JSON representation of a checkpoint
2259 2259 */
2260 2260 Notebook.prototype.list_checkpoints_success = function (data) {
2261 2261 this.checkpoints = data;
2262 2262 if (data.length) {
2263 2263 this.last_checkpoint = data[data.length - 1];
2264 2264 } else {
2265 2265 this.last_checkpoint = null;
2266 2266 }
2267 2267 this.events.trigger('checkpoints_listed.Notebook', [data]);
2268 2268 };
2269 2269
2270 2270 /**
2271 2271 * Create a checkpoint of this notebook on the server from the most recent save.
2272 2272 */
2273 2273 Notebook.prototype.create_checkpoint = function () {
2274 2274 var that = this;
2275 2275 this.contents.create_checkpoint(this.notebook_path).then(
2276 2276 $.proxy(this.create_checkpoint_success, this),
2277 2277 function (error) {
2278 2278 that.events.trigger('checkpoint_failed.Notebook', error);
2279 2279 }
2280 2280 );
2281 2281 };
2282 2282
2283 2283 /**
2284 2284 * Success callback for creating a checkpoint.
2285 2285 *
2286 2286 * @param {Object} data JSON representation of a checkpoint
2287 2287 */
2288 2288 Notebook.prototype.create_checkpoint_success = function (data) {
2289 2289 this.add_checkpoint(data);
2290 2290 this.events.trigger('checkpoint_created.Notebook', data);
2291 2291 };
2292 2292
2293 2293 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2294 2294 var that = this;
2295 2295 checkpoint = checkpoint || this.last_checkpoint;
2296 2296 if ( ! checkpoint ) {
2297 2297 console.log("restore dialog, but no checkpoint to restore to!");
2298 2298 return;
2299 2299 }
2300 2300 var body = $('<div/>').append(
2301 2301 $('<p/>').addClass("p-space").text(
2302 2302 "Are you sure you want to revert the notebook to " +
2303 2303 "the latest checkpoint?"
2304 2304 ).append(
2305 2305 $("<strong/>").text(
2306 2306 " This cannot be undone."
2307 2307 )
2308 2308 )
2309 2309 ).append(
2310 2310 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2311 2311 ).append(
2312 2312 $('<p/>').addClass("p-space").text(
2313 2313 Date(checkpoint.last_modified)
2314 2314 ).css("text-align", "center")
2315 2315 );
2316 2316
2317 2317 dialog.modal({
2318 2318 notebook: this,
2319 2319 keyboard_manager: this.keyboard_manager,
2320 2320 title : "Revert notebook to checkpoint",
2321 2321 body : body,
2322 2322 buttons : {
2323 2323 Revert : {
2324 2324 class : "btn-danger",
2325 2325 click : function () {
2326 2326 that.restore_checkpoint(checkpoint.id);
2327 2327 }
2328 2328 },
2329 2329 Cancel : {}
2330 2330 }
2331 2331 });
2332 2332 };
2333 2333
2334 2334 /**
2335 2335 * Restore the notebook to a checkpoint state.
2336 2336 *
2337 2337 * @param {String} checkpoint ID
2338 2338 */
2339 2339 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2340 2340 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2341 2341 var that = this;
2342 2342 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2343 2343 $.proxy(this.restore_checkpoint_success, this),
2344 2344 function (error) {
2345 2345 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2346 2346 }
2347 2347 );
2348 2348 };
2349 2349
2350 2350 /**
2351 2351 * Success callback for restoring a notebook to a checkpoint.
2352 2352 */
2353 2353 Notebook.prototype.restore_checkpoint_success = function () {
2354 2354 this.events.trigger('checkpoint_restored.Notebook');
2355 2355 this.load_notebook(this.notebook_path);
2356 2356 };
2357 2357
2358 2358 /**
2359 2359 * Delete a notebook checkpoint.
2360 2360 *
2361 2361 * @param {String} checkpoint ID
2362 2362 */
2363 2363 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2364 2364 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2365 2365 var that = this;
2366 2366 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2367 2367 $.proxy(this.delete_checkpoint_success, this),
2368 2368 function (error) {
2369 2369 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2370 2370 }
2371 2371 );
2372 2372 };
2373 2373
2374 2374 /**
2375 2375 * Success callback for deleting a notebook checkpoint
2376 2376 */
2377 2377 Notebook.prototype.delete_checkpoint_success = function () {
2378 2378 this.events.trigger('checkpoint_deleted.Notebook');
2379 2379 this.load_notebook(this.notebook_path);
2380 2380 };
2381 2381
2382 2382
2383 2383 // For backwards compatability.
2384 2384 IPython.Notebook = Notebook;
2385 2385
2386 2386 return {'Notebook': Notebook};
2387 2387 });
@@ -1,19 +1,20 b''
1 1 {
2 "tags": {
3 "allowUnknownTags": true
2 "markdown": {
3 "parser": "gfm"
4 4 },
5 "plugins": [
6 "plugins/markdown"
7 ],
5 8 "source": {
6 9 "include": [
7 10 "../IPython/html/static/notebook/js/notebook.js"
8 11 ]
9 12 },
10 "plugins": [],
13 "tags": {
14 "allowUnknownTags": true
15 },
11 16 "templates": {
12 17 "cleverLinks": false,
13 18 "monospaceLinks": false
14 },
15 "plugins": [ "plugins/markdown" ],
16 "markdown": {
17 "parser": "gfm"
18 19 }
19 } No newline at end of file
20 }
@@ -1,77 +1,77 b''
1 1 @ECHO OFF
2 2 REM ~ Windows command line make file for Sphinx documentation
3 3
4 4 SETLOCAL
5 5
6 6 SET SPHINXOPTS=
7 7 SET SPHINXBUILD=sphinx-build
8 8 SET JSDOC=jsdoc
9 9 SET PAPER=
10 10 SET SRCDIR=source
11 11
12 12 IF "%PAPER%" == "" SET PAPER=a4
13 13 SET ALLSPHINXOPTS=-d build\doctrees -D latex_paper_size=%PAPER% %SPHINXOPTS% %SRCDIR%
14 14
15 15 FOR %%X IN (%SPHINXBUILD%.exe) DO SET P=%%~$PATH:X
16 16
17 17 FOR %%L IN (html pickle htmlhelp latex changes linkcheck) DO (
18 18 IF "%1" == "%%L" (
19 19 IF "%P%" == "" (
20 20 ECHO.
21 21 ECHO Error: Sphinx is not available. Please make sure it is correctly installed.
22 22 GOTO END
23 23 )
24 24 MD build\doctrees 2>NUL
25 25 MD build\%1 || GOTO DIR_EXIST
26 26 %SPHINXBUILD% -b %1 %ALLSPHINXOPTS% build\%1
27 27 IF NOT ERRORLEVEL 0 GOTO ERROR
28 28 ECHO.
29 29 ECHO Build finished. Results are in build\%1.
30 30 IF "%1" == "pickle" (
31 31 ECHO Now you can process the pickle files or run
32 32 ECHO sphinx-web build\pickle to start the sphinx-web server.
33 33 )
34 34 IF "%1" == "htmlhelp" (
35 35 ECHO Now you can run HTML Help Workshop with the
36 36 ECHO .hhp project file in build/htmlhelp.
37 37 )
38 38 IF "%1" == "linkcheck" (
39 39 ECHO Look for any errors in the above output
40 40 ECHO or in build\linkcheck\output.rst.
41 41 )
42 42 GOTO END
43 43 )
44 44 )
45 45
46 IF "%1" == "clean" (
46 IF "%1" == "jsapi" (
47 47 %JSDOC% -c jsdoc_config.json -d ./build/jsapi_html/
48 48 )
49 49
50 IF "%1" == "jsapi" (
50 IF "%1" == "clean" (
51 51 RD /s /q build dist %SRCDIR%\api\generated 2>NUL
52 52 IF ERRORLEVEL 0 ECHO Build environment cleaned!
53 53 GOTO END
54 54 )
55 55
56 56 ECHO.
57 57 ECHO Please use "make [target]" where [target] is one of:
58 58 ECHO.
59 59 ECHO html to make standalone HTML files
60 60 ECHO jsapi to make standalone HTML files for the Javascript API
61 61 ECHO pickle to make pickle files (usable by e.g. sphinx-web)
62 62 ECHO htmlhelp to make HTML files and a HTML help project
63 63 ECHO latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
64 64 ECHO changes to make an overview over all changed/added/deprecated items
65 65 ECHO linkcheck to check all external links for integrity
66 66 GOTO END
67 67
68 68 :DIR_EXIST
69 69 ECHO.
70 70 ECHO Info: Run "make clean" to clean build environment
71 71
72 72 :ERROR
73 73 ECHO.
74 74 ECHO Error: Build process failed!
75 75
76 76 :END
77 77 ENDLOCAL No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now