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