##// END OF EJS Templates
Added shorcuts to split cell, merge cell above and merge cell below.
damianavila -
Show More
@@ -1,2030 +1,2045 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15 var key = IPython.utils.keycodes;
16 16
17 17 /**
18 18 * A notebook contains and manages cells.
19 19 *
20 20 * @class Notebook
21 21 * @constructor
22 22 * @param {String} selector A jQuery selector for the notebook's DOM element
23 23 * @param {Object} [options] A config object
24 24 */
25 25 var Notebook = function (selector, options) {
26 26 var options = options || {};
27 27 this._baseProjectUrl = options.baseProjectUrl;
28 28 this.read_only = options.read_only || IPython.read_only;
29 29
30 30 this.element = $(selector);
31 31 this.element.scroll();
32 32 this.element.data("notebook", this);
33 33 this.next_prompt_number = 1;
34 34 this.kernel = null;
35 35 this.clipboard = null;
36 36 this.undelete_backup = null;
37 37 this.undelete_index = null;
38 38 this.undelete_below = false;
39 39 this.paste_enabled = false;
40 40 this.set_dirty(false);
41 41 this.metadata = {};
42 42 this._checkpoint_after_save = false;
43 43 this.last_checkpoint = null;
44 44 this.autosave_interval = 0;
45 45 this.autosave_timer = null;
46 46 // autosave *at most* every two minutes
47 47 this.minimum_autosave_interval = 120000;
48 48 // single worksheet for now
49 49 this.worksheet_metadata = {};
50 50 this.control_key_active = false;
51 51 this.notebook_id = null;
52 52 this.notebook_name = null;
53 53 this.notebook_name_blacklist_re = /[\/\\:]/;
54 54 this.nbformat = 3 // Increment this when changing the nbformat
55 55 this.nbformat_minor = 0 // Increment this when changing the nbformat
56 56 this.style();
57 57 this.create_elements();
58 58 this.bind_events();
59 59 };
60 60
61 61 /**
62 62 * Tweak the notebook's CSS style.
63 63 *
64 64 * @method style
65 65 */
66 66 Notebook.prototype.style = function () {
67 67 $('div#notebook').addClass('border-box-sizing');
68 68 };
69 69
70 70 /**
71 71 * Get the root URL of the notebook server.
72 72 *
73 73 * @method baseProjectUrl
74 74 * @return {String} The base project URL
75 75 */
76 76 Notebook.prototype.baseProjectUrl = function(){
77 77 return this._baseProjectUrl || $('body').data('baseProjectUrl');
78 78 };
79 79
80 80 /**
81 81 * Create an HTML and CSS representation of the notebook.
82 82 *
83 83 * @method create_elements
84 84 */
85 85 Notebook.prototype.create_elements = function () {
86 86 // We add this end_space div to the end of the notebook div to:
87 87 // i) provide a margin between the last cell and the end of the notebook
88 88 // ii) to prevent the div from scrolling up when the last cell is being
89 89 // edited, but is too low on the page, which browsers will do automatically.
90 90 var that = this;
91 91 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
92 92 var end_space = $('<div/>').addClass('end_space');
93 93 end_space.dblclick(function (e) {
94 94 if (that.read_only) return;
95 95 var ncells = that.ncells();
96 96 that.insert_cell_below('code',ncells-1);
97 97 });
98 98 this.element.append(this.container);
99 99 this.container.append(end_space);
100 100 $('div#notebook').addClass('border-box-sizing');
101 101 };
102 102
103 103 /**
104 104 * Bind JavaScript events: key presses and custom IPython events.
105 105 *
106 106 * @method bind_events
107 107 */
108 108 Notebook.prototype.bind_events = function () {
109 109 var that = this;
110 110
111 111 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
112 112 var index = that.find_cell_index(data.cell);
113 113 var new_cell = that.insert_cell_below('code',index);
114 114 new_cell.set_text(data.text);
115 115 that.dirty = true;
116 116 });
117 117
118 118 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
119 119 that.dirty = data.value;
120 120 });
121 121
122 122 $([IPython.events]).on('select.Cell', function (event, data) {
123 123 var index = that.find_cell_index(data.cell);
124 124 that.select(index);
125 125 });
126 126
127 127 $([IPython.events]).on('status_autorestarting.Kernel', function () {
128 128 IPython.dialog.modal({
129 129 title: "Kernel Restarting",
130 130 body: "The kernel appears to have died. It will restart automatically.",
131 131 buttons: {
132 132 OK : {
133 133 class : "btn-primary"
134 134 }
135 135 }
136 136 });
137 137 });
138 138
139 139
140 140 $(document).keydown(function (event) {
141 141 // console.log(event);
142 142 if (that.read_only) return true;
143 143
144 144 // Save (CTRL+S) or (AppleKey+S)
145 145 //metaKey = applekey on mac
146 146 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
147 147 that.save_checkpoint();
148 148 event.preventDefault();
149 149 return false;
150 150 } else if (event.which === key.ESC) {
151 151 // Intercept escape at highest level to avoid closing
152 152 // websocket connection with firefox
153 153 IPython.pager.collapse();
154 154 event.preventDefault();
155 155 } else if (event.which === key.SHIFT) {
156 156 // ignore shift keydown
157 157 return true;
158 158 }
159 159 if (event.which === key.UPARROW && !event.shiftKey) {
160 160 var cell = that.get_selected_cell();
161 161 if (cell && cell.at_top()) {
162 162 event.preventDefault();
163 163 that.select_prev();
164 164 };
165 165 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
166 166 var cell = that.get_selected_cell();
167 167 if (cell && cell.at_bottom()) {
168 168 event.preventDefault();
169 169 that.select_next();
170 170 };
171 171 } else if (event.which === key.ENTER && event.shiftKey) {
172 172 that.execute_selected_cell();
173 173 return false;
174 174 } else if (event.which === key.ENTER && event.altKey) {
175 175 // Execute code cell, and insert new in place
176 176 that.execute_selected_cell();
177 177 // Only insert a new cell, if we ended up in an already populated cell
178 178 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
179 179 that.insert_cell_above('code');
180 180 }
181 181 return false;
182 182 } else if (event.which === key.ENTER && event.ctrlKey) {
183 183 that.execute_selected_cell({terminal:true});
184 184 return false;
185 185 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
186 186 that.control_key_active = true;
187 187 return false;
188 188 } else if (event.which === 88 && that.control_key_active) {
189 189 // Cut selected cell = x
190 190 that.cut_cell();
191 191 that.control_key_active = false;
192 192 return false;
193 193 } else if (event.which === 67 && that.control_key_active) {
194 194 // Copy selected cell = c
195 195 that.copy_cell();
196 196 that.control_key_active = false;
197 197 return false;
198 198 } else if (event.which === 86 && that.control_key_active) {
199 199 // Paste below selected cell = v
200 200 that.paste_cell_below();
201 201 that.control_key_active = false;
202 202 return false;
203 203 } else if (event.which === 68 && that.control_key_active) {
204 204 // Delete selected cell = d
205 205 that.delete_cell();
206 206 that.control_key_active = false;
207 207 return false;
208 208 } else if (event.which === 65 && that.control_key_active) {
209 209 // Insert code cell above selected = a
210 210 that.insert_cell_above('code');
211 211 that.control_key_active = false;
212 212 return false;
213 213 } else if (event.which === 66 && that.control_key_active) {
214 214 // Insert code cell below selected = b
215 215 that.insert_cell_below('code');
216 216 that.control_key_active = false;
217 217 return false;
218 218 } else if (event.which === 89 && that.control_key_active) {
219 219 // To code = y
220 220 that.to_code();
221 221 that.control_key_active = false;
222 222 return false;
223 223 } else if (event.which === 77 && that.control_key_active) {
224 224 // To markdown = m
225 225 that.to_markdown();
226 226 that.control_key_active = false;
227 227 return false;
228 228 } else if (event.which === 84 && that.control_key_active) {
229 229 // To Raw = t
230 230 that.to_raw();
231 231 that.control_key_active = false;
232 232 return false;
233 233 } else if (event.which === 49 && that.control_key_active) {
234 234 // To Heading 1 = 1
235 235 that.to_heading(undefined, 1);
236 236 that.control_key_active = false;
237 237 return false;
238 238 } else if (event.which === 50 && that.control_key_active) {
239 239 // To Heading 2 = 2
240 240 that.to_heading(undefined, 2);
241 241 that.control_key_active = false;
242 242 return false;
243 243 } else if (event.which === 51 && that.control_key_active) {
244 244 // To Heading 3 = 3
245 245 that.to_heading(undefined, 3);
246 246 that.control_key_active = false;
247 247 return false;
248 248 } else if (event.which === 52 && that.control_key_active) {
249 249 // To Heading 4 = 4
250 250 that.to_heading(undefined, 4);
251 251 that.control_key_active = false;
252 252 return false;
253 253 } else if (event.which === 53 && that.control_key_active) {
254 254 // To Heading 5 = 5
255 255 that.to_heading(undefined, 5);
256 256 that.control_key_active = false;
257 257 return false;
258 258 } else if (event.which === 54 && that.control_key_active) {
259 259 // To Heading 6 = 6
260 260 that.to_heading(undefined, 6);
261 261 that.control_key_active = false;
262 262 return false;
263 263 } else if (event.which === 79 && that.control_key_active) {
264 264 // Toggle output = o
265 265 if (event.shiftKey){
266 266 that.toggle_output_scroll();
267 267 } else {
268 268 that.toggle_output();
269 269 }
270 270 that.control_key_active = false;
271 271 return false;
272 272 } else if (event.which === 83 && that.control_key_active) {
273 273 // Save notebook = s
274 274 that.save_checkpoint();
275 275 that.control_key_active = false;
276 276 return false;
277 277 } else if (event.which === 74 && that.control_key_active) {
278 278 // Move cell down = j
279 279 that.move_cell_down();
280 280 that.control_key_active = false;
281 281 return false;
282 282 } else if (event.which === 75 && that.control_key_active) {
283 283 // Move cell up = k
284 284 that.move_cell_up();
285 285 that.control_key_active = false;
286 286 return false;
287 287 } else if (event.which === 80 && that.control_key_active) {
288 288 // Select previous = p
289 289 that.select_prev();
290 290 that.control_key_active = false;
291 291 return false;
292 292 } else if (event.which === 78 && that.control_key_active) {
293 293 // Select next = n
294 294 that.select_next();
295 295 that.control_key_active = false;
296 296 return false;
297 297 } else if (event.which === 76 && that.control_key_active) {
298 298 // Toggle line numbers = l
299 299 that.cell_toggle_line_numbers();
300 300 that.control_key_active = false;
301 301 return false;
302 302 } else if (event.which === 73 && that.control_key_active) {
303 303 // Interrupt kernel = i
304 304 that.kernel.interrupt();
305 305 that.control_key_active = false;
306 306 return false;
307 307 } else if (event.which === 190 && that.control_key_active) {
308 308 // Restart kernel = . # matches qt console
309 309 that.restart_kernel();
310 310 that.control_key_active = false;
311 311 return false;
312 312 } else if (event.which === 72 && that.control_key_active) {
313 313 // Show keyboard shortcuts = h
314 314 IPython.quick_help.show_keyboard_shortcuts();
315 315 that.control_key_active = false;
316 316 return false;
317 317 } else if (event.which === 90 && that.control_key_active) {
318 318 // Undo last cell delete = z
319 319 that.undelete();
320 320 that.control_key_active = false;
321 321 return false;
322 } else if (event.which === 55 && that.control_key_active) {
323 // Split cell = w
324 that.split_cell();
325 that.control_key_active = false;
326 return false;
327 } else if (event.which === 56 && that.control_key_active) {
328 // Merge cell above = e
329 that.merge_cell_above();
330 that.control_key_active = false;
331 return false;
332 } else if (event.which === 57 && that.control_key_active) {
333 // Merge cell below = r
334 that.merge_cell_below();
335 that.control_key_active = false;
336 return false;
322 337 } else if (that.control_key_active) {
323 338 that.control_key_active = false;
324 339 return true;
325 340 }
326 341 return true;
327 342 });
328 343
329 344 var collapse_time = function(time){
330 345 var app_height = $('#ipython-main-app').height(); // content height
331 346 var splitter_height = $('div#pager_splitter').outerHeight(true);
332 347 var new_height = app_height - splitter_height;
333 348 that.element.animate({height : new_height + 'px'}, time);
334 349 }
335 350
336 351 this.element.bind('collapse_pager', function (event,extrap) {
337 352 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
338 353 collapse_time(time);
339 354 });
340 355
341 356 var expand_time = function(time) {
342 357 var app_height = $('#ipython-main-app').height(); // content height
343 358 var splitter_height = $('div#pager_splitter').outerHeight(true);
344 359 var pager_height = $('div#pager').outerHeight(true);
345 360 var new_height = app_height - pager_height - splitter_height;
346 361 that.element.animate({height : new_height + 'px'}, time);
347 362 }
348 363
349 364 this.element.bind('expand_pager', function (event, extrap) {
350 365 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
351 366 expand_time(time);
352 367 });
353 368
354 369 // Firefox 22 broke $(window).on("beforeunload")
355 370 // I'm not sure why or how.
356 371 window.onbeforeunload = function (e) {
357 372 // TODO: Make killing the kernel configurable.
358 373 var kill_kernel = false;
359 374 if (kill_kernel) {
360 375 that.kernel.kill();
361 376 }
362 377 // if we are autosaving, trigger an autosave on nav-away.
363 378 // still warn, because if we don't the autosave may fail.
364 379 if (that.dirty && ! that.read_only) {
365 380 if ( that.autosave_interval ) {
366 381 that.save_notebook();
367 382 return "Autosave in progress, latest changes may be lost.";
368 383 } else {
369 384 return "Unsaved changes will be lost.";
370 385 }
371 386 };
372 387 // Null is the *only* return value that will make the browser not
373 388 // pop up the "don't leave" dialog.
374 389 return null;
375 390 };
376 391 };
377 392
378 393 /**
379 394 * Set the dirty flag, and trigger the set_dirty.Notebook event
380 395 *
381 396 * @method set_dirty
382 397 */
383 398 Notebook.prototype.set_dirty = function (value) {
384 399 if (value === undefined) {
385 400 value = true;
386 401 }
387 402 if (this.dirty == value) {
388 403 return;
389 404 }
390 405 $([IPython.events]).trigger('set_dirty.Notebook', {value: value});
391 406 };
392 407
393 408 /**
394 409 * Scroll the top of the page to a given cell.
395 410 *
396 411 * @method scroll_to_cell
397 412 * @param {Number} cell_number An index of the cell to view
398 413 * @param {Number} time Animation time in milliseconds
399 414 * @return {Number} Pixel offset from the top of the container
400 415 */
401 416 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
402 417 var cells = this.get_cells();
403 418 var time = time || 0;
404 419 cell_number = Math.min(cells.length-1,cell_number);
405 420 cell_number = Math.max(0 ,cell_number);
406 421 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
407 422 this.element.animate({scrollTop:scroll_value}, time);
408 423 return scroll_value;
409 424 };
410 425
411 426 /**
412 427 * Scroll to the bottom of the page.
413 428 *
414 429 * @method scroll_to_bottom
415 430 */
416 431 Notebook.prototype.scroll_to_bottom = function () {
417 432 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
418 433 };
419 434
420 435 /**
421 436 * Scroll to the top of the page.
422 437 *
423 438 * @method scroll_to_top
424 439 */
425 440 Notebook.prototype.scroll_to_top = function () {
426 441 this.element.animate({scrollTop:0}, 0);
427 442 };
428 443
429 444
430 445 // Cell indexing, retrieval, etc.
431 446
432 447 /**
433 448 * Get all cell elements in the notebook.
434 449 *
435 450 * @method get_cell_elements
436 451 * @return {jQuery} A selector of all cell elements
437 452 */
438 453 Notebook.prototype.get_cell_elements = function () {
439 454 return this.container.children("div.cell");
440 455 };
441 456
442 457 /**
443 458 * Get a particular cell element.
444 459 *
445 460 * @method get_cell_element
446 461 * @param {Number} index An index of a cell to select
447 462 * @return {jQuery} A selector of the given cell.
448 463 */
449 464 Notebook.prototype.get_cell_element = function (index) {
450 465 var result = null;
451 466 var e = this.get_cell_elements().eq(index);
452 467 if (e.length !== 0) {
453 468 result = e;
454 469 }
455 470 return result;
456 471 };
457 472
458 473 /**
459 474 * Count the cells in this notebook.
460 475 *
461 476 * @method ncells
462 477 * @return {Number} The number of cells in this notebook
463 478 */
464 479 Notebook.prototype.ncells = function () {
465 480 return this.get_cell_elements().length;
466 481 };
467 482
468 483 /**
469 484 * Get all Cell objects in this notebook.
470 485 *
471 486 * @method get_cells
472 487 * @return {Array} This notebook's Cell objects
473 488 */
474 489 // TODO: we are often calling cells as cells()[i], which we should optimize
475 490 // to cells(i) or a new method.
476 491 Notebook.prototype.get_cells = function () {
477 492 return this.get_cell_elements().toArray().map(function (e) {
478 493 return $(e).data("cell");
479 494 });
480 495 };
481 496
482 497 /**
483 498 * Get a Cell object from this notebook.
484 499 *
485 500 * @method get_cell
486 501 * @param {Number} index An index of a cell to retrieve
487 502 * @return {Cell} A particular cell
488 503 */
489 504 Notebook.prototype.get_cell = function (index) {
490 505 var result = null;
491 506 var ce = this.get_cell_element(index);
492 507 if (ce !== null) {
493 508 result = ce.data('cell');
494 509 }
495 510 return result;
496 511 }
497 512
498 513 /**
499 514 * Get the cell below a given cell.
500 515 *
501 516 * @method get_next_cell
502 517 * @param {Cell} cell The provided cell
503 518 * @return {Cell} The next cell
504 519 */
505 520 Notebook.prototype.get_next_cell = function (cell) {
506 521 var result = null;
507 522 var index = this.find_cell_index(cell);
508 523 if (this.is_valid_cell_index(index+1)) {
509 524 result = this.get_cell(index+1);
510 525 }
511 526 return result;
512 527 }
513 528
514 529 /**
515 530 * Get the cell above a given cell.
516 531 *
517 532 * @method get_prev_cell
518 533 * @param {Cell} cell The provided cell
519 534 * @return {Cell} The previous cell
520 535 */
521 536 Notebook.prototype.get_prev_cell = function (cell) {
522 537 // TODO: off-by-one
523 538 // nb.get_prev_cell(nb.get_cell(1)) is null
524 539 var result = null;
525 540 var index = this.find_cell_index(cell);
526 541 if (index !== null && index > 1) {
527 542 result = this.get_cell(index-1);
528 543 }
529 544 return result;
530 545 }
531 546
532 547 /**
533 548 * Get the numeric index of a given cell.
534 549 *
535 550 * @method find_cell_index
536 551 * @param {Cell} cell The provided cell
537 552 * @return {Number} The cell's numeric index
538 553 */
539 554 Notebook.prototype.find_cell_index = function (cell) {
540 555 var result = null;
541 556 this.get_cell_elements().filter(function (index) {
542 557 if ($(this).data("cell") === cell) {
543 558 result = index;
544 559 };
545 560 });
546 561 return result;
547 562 };
548 563
549 564 /**
550 565 * Get a given index , or the selected index if none is provided.
551 566 *
552 567 * @method index_or_selected
553 568 * @param {Number} index A cell's index
554 569 * @return {Number} The given index, or selected index if none is provided.
555 570 */
556 571 Notebook.prototype.index_or_selected = function (index) {
557 572 var i;
558 573 if (index === undefined || index === null) {
559 574 i = this.get_selected_index();
560 575 if (i === null) {
561 576 i = 0;
562 577 }
563 578 } else {
564 579 i = index;
565 580 }
566 581 return i;
567 582 };
568 583
569 584 /**
570 585 * Get the currently selected cell.
571 586 * @method get_selected_cell
572 587 * @return {Cell} The selected cell
573 588 */
574 589 Notebook.prototype.get_selected_cell = function () {
575 590 var index = this.get_selected_index();
576 591 return this.get_cell(index);
577 592 };
578 593
579 594 /**
580 595 * Check whether a cell index is valid.
581 596 *
582 597 * @method is_valid_cell_index
583 598 * @param {Number} index A cell index
584 599 * @return True if the index is valid, false otherwise
585 600 */
586 601 Notebook.prototype.is_valid_cell_index = function (index) {
587 602 if (index !== null && index >= 0 && index < this.ncells()) {
588 603 return true;
589 604 } else {
590 605 return false;
591 606 };
592 607 }
593 608
594 609 /**
595 610 * Get the index of the currently selected cell.
596 611
597 612 * @method get_selected_index
598 613 * @return {Number} The selected cell's numeric index
599 614 */
600 615 Notebook.prototype.get_selected_index = function () {
601 616 var result = null;
602 617 this.get_cell_elements().filter(function (index) {
603 618 if ($(this).data("cell").selected === true) {
604 619 result = index;
605 620 };
606 621 });
607 622 return result;
608 623 };
609 624
610 625
611 626 // Cell selection.
612 627
613 628 /**
614 629 * Programmatically select a cell.
615 630 *
616 631 * @method select
617 632 * @param {Number} index A cell's index
618 633 * @return {Notebook} This notebook
619 634 */
620 635 Notebook.prototype.select = function (index) {
621 636 if (this.is_valid_cell_index(index)) {
622 637 var sindex = this.get_selected_index()
623 638 if (sindex !== null && index !== sindex) {
624 639 this.get_cell(sindex).unselect();
625 640 };
626 641 var cell = this.get_cell(index);
627 642 cell.select();
628 643 if (cell.cell_type === 'heading') {
629 644 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
630 645 {'cell_type':cell.cell_type,level:cell.level}
631 646 );
632 647 } else {
633 648 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
634 649 {'cell_type':cell.cell_type}
635 650 );
636 651 };
637 652 };
638 653 return this;
639 654 };
640 655
641 656 /**
642 657 * Programmatically select the next cell.
643 658 *
644 659 * @method select_next
645 660 * @return {Notebook} This notebook
646 661 */
647 662 Notebook.prototype.select_next = function () {
648 663 var index = this.get_selected_index();
649 664 this.select(index+1);
650 665 return this;
651 666 };
652 667
653 668 /**
654 669 * Programmatically select the previous cell.
655 670 *
656 671 * @method select_prev
657 672 * @return {Notebook} This notebook
658 673 */
659 674 Notebook.prototype.select_prev = function () {
660 675 var index = this.get_selected_index();
661 676 this.select(index-1);
662 677 return this;
663 678 };
664 679
665 680
666 681 // Cell movement
667 682
668 683 /**
669 684 * Move given (or selected) cell up and select it.
670 685 *
671 686 * @method move_cell_up
672 687 * @param [index] {integer} cell index
673 688 * @return {Notebook} This notebook
674 689 **/
675 690 Notebook.prototype.move_cell_up = function (index) {
676 691 var i = this.index_or_selected(index);
677 692 if (this.is_valid_cell_index(i) && i > 0) {
678 693 var pivot = this.get_cell_element(i-1);
679 694 var tomove = this.get_cell_element(i);
680 695 if (pivot !== null && tomove !== null) {
681 696 tomove.detach();
682 697 pivot.before(tomove);
683 698 this.select(i-1);
684 699 };
685 700 this.set_dirty(true);
686 701 };
687 702 return this;
688 703 };
689 704
690 705
691 706 /**
692 707 * Move given (or selected) cell down and select it
693 708 *
694 709 * @method move_cell_down
695 710 * @param [index] {integer} cell index
696 711 * @return {Notebook} This notebook
697 712 **/
698 713 Notebook.prototype.move_cell_down = function (index) {
699 714 var i = this.index_or_selected(index);
700 715 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
701 716 var pivot = this.get_cell_element(i+1);
702 717 var tomove = this.get_cell_element(i);
703 718 if (pivot !== null && tomove !== null) {
704 719 tomove.detach();
705 720 pivot.after(tomove);
706 721 this.select(i+1);
707 722 };
708 723 };
709 724 this.set_dirty();
710 725 return this;
711 726 };
712 727
713 728
714 729 // Insertion, deletion.
715 730
716 731 /**
717 732 * Delete a cell from the notebook.
718 733 *
719 734 * @method delete_cell
720 735 * @param [index] A cell's numeric index
721 736 * @return {Notebook} This notebook
722 737 */
723 738 Notebook.prototype.delete_cell = function (index) {
724 739 var i = this.index_or_selected(index);
725 740 var cell = this.get_selected_cell();
726 741 this.undelete_backup = cell.toJSON();
727 742 $('#undelete_cell').removeClass('disabled');
728 743 if (this.is_valid_cell_index(i)) {
729 744 var ce = this.get_cell_element(i);
730 745 ce.remove();
731 746 if (i === (this.ncells())) {
732 747 this.select(i-1);
733 748 this.undelete_index = i - 1;
734 749 this.undelete_below = true;
735 750 } else {
736 751 this.select(i);
737 752 this.undelete_index = i;
738 753 this.undelete_below = false;
739 754 };
740 755 this.set_dirty(true);
741 756 };
742 757 return this;
743 758 };
744 759
745 760 /**
746 761 * Insert a cell so that after insertion the cell is at given index.
747 762 *
748 763 * Similar to insert_above, but index parameter is mandatory
749 764 *
750 765 * Index will be brought back into the accissible range [0,n]
751 766 *
752 767 * @method insert_cell_at_index
753 768 * @param type {string} in ['code','markdown','heading']
754 769 * @param [index] {int} a valid index where to inser cell
755 770 *
756 771 * @return cell {cell|null} created cell or null
757 772 **/
758 773 Notebook.prototype.insert_cell_at_index = function(type, index){
759 774
760 775 var ncells = this.ncells();
761 776 var index = Math.min(index,ncells);
762 777 index = Math.max(index,0);
763 778 var cell = null;
764 779
765 780 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
766 781 if (type === 'code') {
767 782 cell = new IPython.CodeCell(this.kernel);
768 783 cell.set_input_prompt();
769 784 } else if (type === 'markdown') {
770 785 cell = new IPython.MarkdownCell();
771 786 } else if (type === 'raw') {
772 787 cell = new IPython.RawCell();
773 788 } else if (type === 'heading') {
774 789 cell = new IPython.HeadingCell();
775 790 }
776 791
777 792 if(this._insert_element_at_index(cell.element,index)){
778 793 cell.render();
779 794 this.select(this.find_cell_index(cell));
780 795 this.set_dirty(true);
781 796 }
782 797 }
783 798 return cell;
784 799
785 800 };
786 801
787 802 /**
788 803 * Insert an element at given cell index.
789 804 *
790 805 * @method _insert_element_at_index
791 806 * @param element {dom element} a cell element
792 807 * @param [index] {int} a valid index where to inser cell
793 808 * @private
794 809 *
795 810 * return true if everything whent fine.
796 811 **/
797 812 Notebook.prototype._insert_element_at_index = function(element, index){
798 813 if (element === undefined){
799 814 return false;
800 815 }
801 816
802 817 var ncells = this.ncells();
803 818
804 819 if (ncells === 0) {
805 820 // special case append if empty
806 821 this.element.find('div.end_space').before(element);
807 822 } else if ( ncells === index ) {
808 823 // special case append it the end, but not empty
809 824 this.get_cell_element(index-1).after(element);
810 825 } else if (this.is_valid_cell_index(index)) {
811 826 // otherwise always somewhere to append to
812 827 this.get_cell_element(index).before(element);
813 828 } else {
814 829 return false;
815 830 }
816 831
817 832 if (this.undelete_index !== null && index <= this.undelete_index) {
818 833 this.undelete_index = this.undelete_index + 1;
819 834 this.set_dirty(true);
820 835 }
821 836 return true;
822 837 };
823 838
824 839 /**
825 840 * Insert a cell of given type above given index, or at top
826 841 * of notebook if index smaller than 0.
827 842 *
828 843 * default index value is the one of currently selected cell
829 844 *
830 845 * @method insert_cell_above
831 846 * @param type {string} cell type
832 847 * @param [index] {integer}
833 848 *
834 849 * @return handle to created cell or null
835 850 **/
836 851 Notebook.prototype.insert_cell_above = function (type, index) {
837 852 index = this.index_or_selected(index);
838 853 return this.insert_cell_at_index(type, index);
839 854 };
840 855
841 856 /**
842 857 * Insert a cell of given type below given index, or at bottom
843 858 * of notebook if index greater thatn number of cell
844 859 *
845 860 * default index value is the one of currently selected cell
846 861 *
847 862 * @method insert_cell_below
848 863 * @param type {string} cell type
849 864 * @param [index] {integer}
850 865 *
851 866 * @return handle to created cell or null
852 867 *
853 868 **/
854 869 Notebook.prototype.insert_cell_below = function (type, index) {
855 870 index = this.index_or_selected(index);
856 871 return this.insert_cell_at_index(type, index+1);
857 872 };
858 873
859 874
860 875 /**
861 876 * Insert cell at end of notebook
862 877 *
863 878 * @method insert_cell_at_bottom
864 879 * @param {String} type cell type
865 880 *
866 881 * @return the added cell; or null
867 882 **/
868 883 Notebook.prototype.insert_cell_at_bottom = function (type){
869 884 var len = this.ncells();
870 885 return this.insert_cell_below(type,len-1);
871 886 };
872 887
873 888 /**
874 889 * Turn a cell into a code cell.
875 890 *
876 891 * @method to_code
877 892 * @param {Number} [index] A cell's index
878 893 */
879 894 Notebook.prototype.to_code = function (index) {
880 895 var i = this.index_or_selected(index);
881 896 if (this.is_valid_cell_index(i)) {
882 897 var source_element = this.get_cell_element(i);
883 898 var source_cell = source_element.data("cell");
884 899 if (!(source_cell instanceof IPython.CodeCell)) {
885 900 var target_cell = this.insert_cell_below('code',i);
886 901 var text = source_cell.get_text();
887 902 if (text === source_cell.placeholder) {
888 903 text = '';
889 904 }
890 905 target_cell.set_text(text);
891 906 // make this value the starting point, so that we can only undo
892 907 // to this state, instead of a blank cell
893 908 target_cell.code_mirror.clearHistory();
894 909 source_element.remove();
895 910 this.set_dirty(true);
896 911 };
897 912 };
898 913 };
899 914
900 915 /**
901 916 * Turn a cell into a Markdown cell.
902 917 *
903 918 * @method to_markdown
904 919 * @param {Number} [index] A cell's index
905 920 */
906 921 Notebook.prototype.to_markdown = function (index) {
907 922 var i = this.index_or_selected(index);
908 923 if (this.is_valid_cell_index(i)) {
909 924 var source_element = this.get_cell_element(i);
910 925 var source_cell = source_element.data("cell");
911 926 if (!(source_cell instanceof IPython.MarkdownCell)) {
912 927 var target_cell = this.insert_cell_below('markdown',i);
913 928 var text = source_cell.get_text();
914 929 if (text === source_cell.placeholder) {
915 930 text = '';
916 931 };
917 932 // The edit must come before the set_text.
918 933 target_cell.edit();
919 934 target_cell.set_text(text);
920 935 // make this value the starting point, so that we can only undo
921 936 // to this state, instead of a blank cell
922 937 target_cell.code_mirror.clearHistory();
923 938 source_element.remove();
924 939 this.set_dirty(true);
925 940 };
926 941 };
927 942 };
928 943
929 944 /**
930 945 * Turn a cell into a raw text cell.
931 946 *
932 947 * @method to_raw
933 948 * @param {Number} [index] A cell's index
934 949 */
935 950 Notebook.prototype.to_raw = function (index) {
936 951 var i = this.index_or_selected(index);
937 952 if (this.is_valid_cell_index(i)) {
938 953 var source_element = this.get_cell_element(i);
939 954 var source_cell = source_element.data("cell");
940 955 var target_cell = null;
941 956 if (!(source_cell instanceof IPython.RawCell)) {
942 957 target_cell = this.insert_cell_below('raw',i);
943 958 var text = source_cell.get_text();
944 959 if (text === source_cell.placeholder) {
945 960 text = '';
946 961 };
947 962 // The edit must come before the set_text.
948 963 target_cell.edit();
949 964 target_cell.set_text(text);
950 965 // make this value the starting point, so that we can only undo
951 966 // to this state, instead of a blank cell
952 967 target_cell.code_mirror.clearHistory();
953 968 source_element.remove();
954 969 this.set_dirty(true);
955 970 };
956 971 };
957 972 };
958 973
959 974 /**
960 975 * Turn a cell into a heading cell.
961 976 *
962 977 * @method to_heading
963 978 * @param {Number} [index] A cell's index
964 979 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
965 980 */
966 981 Notebook.prototype.to_heading = function (index, level) {
967 982 level = level || 1;
968 983 var i = this.index_or_selected(index);
969 984 if (this.is_valid_cell_index(i)) {
970 985 var source_element = this.get_cell_element(i);
971 986 var source_cell = source_element.data("cell");
972 987 var target_cell = null;
973 988 if (source_cell instanceof IPython.HeadingCell) {
974 989 source_cell.set_level(level);
975 990 } else {
976 991 target_cell = this.insert_cell_below('heading',i);
977 992 var text = source_cell.get_text();
978 993 if (text === source_cell.placeholder) {
979 994 text = '';
980 995 };
981 996 // The edit must come before the set_text.
982 997 target_cell.set_level(level);
983 998 target_cell.edit();
984 999 target_cell.set_text(text);
985 1000 // make this value the starting point, so that we can only undo
986 1001 // to this state, instead of a blank cell
987 1002 target_cell.code_mirror.clearHistory();
988 1003 source_element.remove();
989 1004 this.set_dirty(true);
990 1005 };
991 1006 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
992 1007 {'cell_type':'heading',level:level}
993 1008 );
994 1009 };
995 1010 };
996 1011
997 1012
998 1013 // Cut/Copy/Paste
999 1014
1000 1015 /**
1001 1016 * Enable UI elements for pasting cells.
1002 1017 *
1003 1018 * @method enable_paste
1004 1019 */
1005 1020 Notebook.prototype.enable_paste = function () {
1006 1021 var that = this;
1007 1022 if (!this.paste_enabled) {
1008 1023 $('#paste_cell_replace').removeClass('disabled')
1009 1024 .on('click', function () {that.paste_cell_replace();});
1010 1025 $('#paste_cell_above').removeClass('disabled')
1011 1026 .on('click', function () {that.paste_cell_above();});
1012 1027 $('#paste_cell_below').removeClass('disabled')
1013 1028 .on('click', function () {that.paste_cell_below();});
1014 1029 this.paste_enabled = true;
1015 1030 };
1016 1031 };
1017 1032
1018 1033 /**
1019 1034 * Disable UI elements for pasting cells.
1020 1035 *
1021 1036 * @method disable_paste
1022 1037 */
1023 1038 Notebook.prototype.disable_paste = function () {
1024 1039 if (this.paste_enabled) {
1025 1040 $('#paste_cell_replace').addClass('disabled').off('click');
1026 1041 $('#paste_cell_above').addClass('disabled').off('click');
1027 1042 $('#paste_cell_below').addClass('disabled').off('click');
1028 1043 this.paste_enabled = false;
1029 1044 };
1030 1045 };
1031 1046
1032 1047 /**
1033 1048 * Cut a cell.
1034 1049 *
1035 1050 * @method cut_cell
1036 1051 */
1037 1052 Notebook.prototype.cut_cell = function () {
1038 1053 this.copy_cell();
1039 1054 this.delete_cell();
1040 1055 }
1041 1056
1042 1057 /**
1043 1058 * Copy a cell.
1044 1059 *
1045 1060 * @method copy_cell
1046 1061 */
1047 1062 Notebook.prototype.copy_cell = function () {
1048 1063 var cell = this.get_selected_cell();
1049 1064 this.clipboard = cell.toJSON();
1050 1065 this.enable_paste();
1051 1066 };
1052 1067
1053 1068 /**
1054 1069 * Replace the selected cell with a cell in the clipboard.
1055 1070 *
1056 1071 * @method paste_cell_replace
1057 1072 */
1058 1073 Notebook.prototype.paste_cell_replace = function () {
1059 1074 if (this.clipboard !== null && this.paste_enabled) {
1060 1075 var cell_data = this.clipboard;
1061 1076 var new_cell = this.insert_cell_above(cell_data.cell_type);
1062 1077 new_cell.fromJSON(cell_data);
1063 1078 var old_cell = this.get_next_cell(new_cell);
1064 1079 this.delete_cell(this.find_cell_index(old_cell));
1065 1080 this.select(this.find_cell_index(new_cell));
1066 1081 };
1067 1082 };
1068 1083
1069 1084 /**
1070 1085 * Paste a cell from the clipboard above the selected cell.
1071 1086 *
1072 1087 * @method paste_cell_above
1073 1088 */
1074 1089 Notebook.prototype.paste_cell_above = function () {
1075 1090 if (this.clipboard !== null && this.paste_enabled) {
1076 1091 var cell_data = this.clipboard;
1077 1092 var new_cell = this.insert_cell_above(cell_data.cell_type);
1078 1093 new_cell.fromJSON(cell_data);
1079 1094 };
1080 1095 };
1081 1096
1082 1097 /**
1083 1098 * Paste a cell from the clipboard below the selected cell.
1084 1099 *
1085 1100 * @method paste_cell_below
1086 1101 */
1087 1102 Notebook.prototype.paste_cell_below = function () {
1088 1103 if (this.clipboard !== null && this.paste_enabled) {
1089 1104 var cell_data = this.clipboard;
1090 1105 var new_cell = this.insert_cell_below(cell_data.cell_type);
1091 1106 new_cell.fromJSON(cell_data);
1092 1107 };
1093 1108 };
1094 1109
1095 1110 // Cell undelete
1096 1111
1097 1112 /**
1098 1113 * Restore the most recently deleted cell.
1099 1114 *
1100 1115 * @method undelete
1101 1116 */
1102 1117 Notebook.prototype.undelete = function() {
1103 1118 if (this.undelete_backup !== null && this.undelete_index !== null) {
1104 1119 var current_index = this.get_selected_index();
1105 1120 if (this.undelete_index < current_index) {
1106 1121 current_index = current_index + 1;
1107 1122 }
1108 1123 if (this.undelete_index >= this.ncells()) {
1109 1124 this.select(this.ncells() - 1);
1110 1125 }
1111 1126 else {
1112 1127 this.select(this.undelete_index);
1113 1128 }
1114 1129 var cell_data = this.undelete_backup;
1115 1130 var new_cell = null;
1116 1131 if (this.undelete_below) {
1117 1132 new_cell = this.insert_cell_below(cell_data.cell_type);
1118 1133 } else {
1119 1134 new_cell = this.insert_cell_above(cell_data.cell_type);
1120 1135 }
1121 1136 new_cell.fromJSON(cell_data);
1122 1137 this.select(current_index);
1123 1138 this.undelete_backup = null;
1124 1139 this.undelete_index = null;
1125 1140 }
1126 1141 $('#undelete_cell').addClass('disabled');
1127 1142 }
1128 1143
1129 1144 // Split/merge
1130 1145
1131 1146 /**
1132 1147 * Split the selected cell into two, at the cursor.
1133 1148 *
1134 1149 * @method split_cell
1135 1150 */
1136 1151 Notebook.prototype.split_cell = function () {
1137 1152 // Todo: implement spliting for other cell types.
1138 1153 var cell = this.get_selected_cell();
1139 1154 if (cell.is_splittable()) {
1140 1155 var texta = cell.get_pre_cursor();
1141 1156 var textb = cell.get_post_cursor();
1142 1157 if (cell instanceof IPython.CodeCell) {
1143 1158 cell.set_text(texta);
1144 1159 var new_cell = this.insert_cell_below('code');
1145 1160 new_cell.set_text(textb);
1146 1161 } else if (cell instanceof IPython.MarkdownCell) {
1147 1162 cell.set_text(texta);
1148 1163 cell.render();
1149 1164 var new_cell = this.insert_cell_below('markdown');
1150 1165 new_cell.edit(); // editor must be visible to call set_text
1151 1166 new_cell.set_text(textb);
1152 1167 new_cell.render();
1153 1168 }
1154 1169 };
1155 1170 };
1156 1171
1157 1172 /**
1158 1173 * Combine the selected cell into the cell above it.
1159 1174 *
1160 1175 * @method merge_cell_above
1161 1176 */
1162 1177 Notebook.prototype.merge_cell_above = function () {
1163 1178 var index = this.get_selected_index();
1164 1179 var cell = this.get_cell(index);
1165 1180 if (index > 0) {
1166 1181 var upper_cell = this.get_cell(index-1);
1167 1182 var upper_text = upper_cell.get_text();
1168 1183 var text = cell.get_text();
1169 1184 if (cell instanceof IPython.CodeCell) {
1170 1185 cell.set_text(upper_text+'\n'+text);
1171 1186 } else if (cell instanceof IPython.MarkdownCell) {
1172 1187 cell.edit();
1173 1188 cell.set_text(upper_text+'\n'+text);
1174 1189 cell.render();
1175 1190 };
1176 1191 this.delete_cell(index-1);
1177 1192 this.select(this.find_cell_index(cell));
1178 1193 };
1179 1194 };
1180 1195
1181 1196 /**
1182 1197 * Combine the selected cell into the cell below it.
1183 1198 *
1184 1199 * @method merge_cell_below
1185 1200 */
1186 1201 Notebook.prototype.merge_cell_below = function () {
1187 1202 var index = this.get_selected_index();
1188 1203 var cell = this.get_cell(index);
1189 1204 if (index < this.ncells()-1) {
1190 1205 var lower_cell = this.get_cell(index+1);
1191 1206 var lower_text = lower_cell.get_text();
1192 1207 var text = cell.get_text();
1193 1208 if (cell instanceof IPython.CodeCell) {
1194 1209 cell.set_text(text+'\n'+lower_text);
1195 1210 } else if (cell instanceof IPython.MarkdownCell) {
1196 1211 cell.edit();
1197 1212 cell.set_text(text+'\n'+lower_text);
1198 1213 cell.render();
1199 1214 };
1200 1215 this.delete_cell(index+1);
1201 1216 this.select(this.find_cell_index(cell));
1202 1217 };
1203 1218 };
1204 1219
1205 1220
1206 1221 // Cell collapsing and output clearing
1207 1222
1208 1223 /**
1209 1224 * Hide a cell's output.
1210 1225 *
1211 1226 * @method collapse
1212 1227 * @param {Number} index A cell's numeric index
1213 1228 */
1214 1229 Notebook.prototype.collapse = function (index) {
1215 1230 var i = this.index_or_selected(index);
1216 1231 this.get_cell(i).collapse();
1217 1232 this.set_dirty(true);
1218 1233 };
1219 1234
1220 1235 /**
1221 1236 * Show a cell's output.
1222 1237 *
1223 1238 * @method expand
1224 1239 * @param {Number} index A cell's numeric index
1225 1240 */
1226 1241 Notebook.prototype.expand = function (index) {
1227 1242 var i = this.index_or_selected(index);
1228 1243 this.get_cell(i).expand();
1229 1244 this.set_dirty(true);
1230 1245 };
1231 1246
1232 1247 /** Toggle whether a cell's output is collapsed or expanded.
1233 1248 *
1234 1249 * @method toggle_output
1235 1250 * @param {Number} index A cell's numeric index
1236 1251 */
1237 1252 Notebook.prototype.toggle_output = function (index) {
1238 1253 var i = this.index_or_selected(index);
1239 1254 this.get_cell(i).toggle_output();
1240 1255 this.set_dirty(true);
1241 1256 };
1242 1257
1243 1258 /**
1244 1259 * Toggle a scrollbar for long cell outputs.
1245 1260 *
1246 1261 * @method toggle_output_scroll
1247 1262 * @param {Number} index A cell's numeric index
1248 1263 */
1249 1264 Notebook.prototype.toggle_output_scroll = function (index) {
1250 1265 var i = this.index_or_selected(index);
1251 1266 this.get_cell(i).toggle_output_scroll();
1252 1267 };
1253 1268
1254 1269 /**
1255 1270 * Hide each code cell's output area.
1256 1271 *
1257 1272 * @method collapse_all_output
1258 1273 */
1259 1274 Notebook.prototype.collapse_all_output = function () {
1260 1275 var ncells = this.ncells();
1261 1276 var cells = this.get_cells();
1262 1277 for (var i=0; i<ncells; i++) {
1263 1278 if (cells[i] instanceof IPython.CodeCell) {
1264 1279 cells[i].output_area.collapse();
1265 1280 }
1266 1281 };
1267 1282 // this should not be set if the `collapse` key is removed from nbformat
1268 1283 this.set_dirty(true);
1269 1284 };
1270 1285
1271 1286 /**
1272 1287 * Expand each code cell's output area, and add a scrollbar for long output.
1273 1288 *
1274 1289 * @method scroll_all_output
1275 1290 */
1276 1291 Notebook.prototype.scroll_all_output = function () {
1277 1292 var ncells = this.ncells();
1278 1293 var cells = this.get_cells();
1279 1294 for (var i=0; i<ncells; i++) {
1280 1295 if (cells[i] instanceof IPython.CodeCell) {
1281 1296 cells[i].output_area.expand();
1282 1297 cells[i].output_area.scroll_if_long();
1283 1298 }
1284 1299 };
1285 1300 // this should not be set if the `collapse` key is removed from nbformat
1286 1301 this.set_dirty(true);
1287 1302 };
1288 1303
1289 1304 /**
1290 1305 * Expand each code cell's output area, and remove scrollbars.
1291 1306 *
1292 1307 * @method expand_all_output
1293 1308 */
1294 1309 Notebook.prototype.expand_all_output = function () {
1295 1310 var ncells = this.ncells();
1296 1311 var cells = this.get_cells();
1297 1312 for (var i=0; i<ncells; i++) {
1298 1313 if (cells[i] instanceof IPython.CodeCell) {
1299 1314 cells[i].output_area.expand();
1300 1315 cells[i].output_area.unscroll_area();
1301 1316 }
1302 1317 };
1303 1318 // this should not be set if the `collapse` key is removed from nbformat
1304 1319 this.set_dirty(true);
1305 1320 };
1306 1321
1307 1322 /**
1308 1323 * Clear each code cell's output area.
1309 1324 *
1310 1325 * @method clear_all_output
1311 1326 */
1312 1327 Notebook.prototype.clear_all_output = function () {
1313 1328 var ncells = this.ncells();
1314 1329 var cells = this.get_cells();
1315 1330 for (var i=0; i<ncells; i++) {
1316 1331 if (cells[i] instanceof IPython.CodeCell) {
1317 1332 cells[i].clear_output(true,true,true);
1318 1333 // Make all In[] prompts blank, as well
1319 1334 // TODO: make this configurable (via checkbox?)
1320 1335 cells[i].set_input_prompt();
1321 1336 }
1322 1337 };
1323 1338 this.set_dirty(true);
1324 1339 };
1325 1340
1326 1341
1327 1342 // Other cell functions: line numbers, ...
1328 1343
1329 1344 /**
1330 1345 * Toggle line numbers in the selected cell's input area.
1331 1346 *
1332 1347 * @method cell_toggle_line_numbers
1333 1348 */
1334 1349 Notebook.prototype.cell_toggle_line_numbers = function() {
1335 1350 this.get_selected_cell().toggle_line_numbers();
1336 1351 };
1337 1352
1338 1353 // Kernel related things
1339 1354
1340 1355 /**
1341 1356 * Start a new kernel and set it on each code cell.
1342 1357 *
1343 1358 * @method start_kernel
1344 1359 */
1345 1360 Notebook.prototype.start_kernel = function () {
1346 1361 var base_url = $('body').data('baseKernelUrl') + "kernels";
1347 1362 this.kernel = new IPython.Kernel(base_url);
1348 1363 this.kernel.start(this.notebook_id);
1349 1364 // Now that the kernel has been created, tell the CodeCells about it.
1350 1365 var ncells = this.ncells();
1351 1366 for (var i=0; i<ncells; i++) {
1352 1367 var cell = this.get_cell(i);
1353 1368 if (cell instanceof IPython.CodeCell) {
1354 1369 cell.set_kernel(this.kernel)
1355 1370 };
1356 1371 };
1357 1372 };
1358 1373
1359 1374 /**
1360 1375 * Prompt the user to restart the IPython kernel.
1361 1376 *
1362 1377 * @method restart_kernel
1363 1378 */
1364 1379 Notebook.prototype.restart_kernel = function () {
1365 1380 var that = this;
1366 1381 IPython.dialog.modal({
1367 1382 title : "Restart kernel or continue running?",
1368 1383 body : $("<p/>").html(
1369 1384 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1370 1385 ),
1371 1386 buttons : {
1372 1387 "Continue running" : {},
1373 1388 "Restart" : {
1374 1389 "class" : "btn-danger",
1375 1390 "click" : function() {
1376 1391 that.kernel.restart();
1377 1392 }
1378 1393 }
1379 1394 }
1380 1395 });
1381 1396 };
1382 1397
1383 1398 /**
1384 1399 * Run the selected cell.
1385 1400 *
1386 1401 * Execute or render cell outputs.
1387 1402 *
1388 1403 * @method execute_selected_cell
1389 1404 * @param {Object} options Customize post-execution behavior
1390 1405 */
1391 1406 Notebook.prototype.execute_selected_cell = function (options) {
1392 1407 // add_new: should a new cell be added if we are at the end of the nb
1393 1408 // terminal: execute in terminal mode, which stays in the current cell
1394 1409 var default_options = {terminal: false, add_new: true};
1395 1410 $.extend(default_options, options);
1396 1411 var that = this;
1397 1412 var cell = that.get_selected_cell();
1398 1413 var cell_index = that.find_cell_index(cell);
1399 1414 if (cell instanceof IPython.CodeCell) {
1400 1415 cell.execute();
1401 1416 }
1402 1417 if (default_options.terminal) {
1403 1418 cell.select_all();
1404 1419 } else {
1405 1420 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1406 1421 that.insert_cell_below('code');
1407 1422 // If we are adding a new cell at the end, scroll down to show it.
1408 1423 that.scroll_to_bottom();
1409 1424 } else {
1410 1425 that.select(cell_index+1);
1411 1426 };
1412 1427 };
1413 1428 this.set_dirty(true);
1414 1429 };
1415 1430
1416 1431 /**
1417 1432 * Execute all cells below the selected cell.
1418 1433 *
1419 1434 * @method execute_cells_below
1420 1435 */
1421 1436 Notebook.prototype.execute_cells_below = function () {
1422 1437 this.execute_cell_range(this.get_selected_index(), this.ncells());
1423 1438 this.scroll_to_bottom();
1424 1439 };
1425 1440
1426 1441 /**
1427 1442 * Execute all cells above the selected cell.
1428 1443 *
1429 1444 * @method execute_cells_above
1430 1445 */
1431 1446 Notebook.prototype.execute_cells_above = function () {
1432 1447 this.execute_cell_range(0, this.get_selected_index());
1433 1448 };
1434 1449
1435 1450 /**
1436 1451 * Execute all cells.
1437 1452 *
1438 1453 * @method execute_all_cells
1439 1454 */
1440 1455 Notebook.prototype.execute_all_cells = function () {
1441 1456 this.execute_cell_range(0, this.ncells());
1442 1457 this.scroll_to_bottom();
1443 1458 };
1444 1459
1445 1460 /**
1446 1461 * Execute a contiguous range of cells.
1447 1462 *
1448 1463 * @method execute_cell_range
1449 1464 * @param {Number} start Index of the first cell to execute (inclusive)
1450 1465 * @param {Number} end Index of the last cell to execute (exclusive)
1451 1466 */
1452 1467 Notebook.prototype.execute_cell_range = function (start, end) {
1453 1468 for (var i=start; i<end; i++) {
1454 1469 this.select(i);
1455 1470 this.execute_selected_cell({add_new:false});
1456 1471 };
1457 1472 };
1458 1473
1459 1474 // Persistance and loading
1460 1475
1461 1476 /**
1462 1477 * Getter method for this notebook's ID.
1463 1478 *
1464 1479 * @method get_notebook_id
1465 1480 * @return {String} This notebook's ID
1466 1481 */
1467 1482 Notebook.prototype.get_notebook_id = function () {
1468 1483 return this.notebook_id;
1469 1484 };
1470 1485
1471 1486 /**
1472 1487 * Getter method for this notebook's name.
1473 1488 *
1474 1489 * @method get_notebook_name
1475 1490 * @return {String} This notebook's name
1476 1491 */
1477 1492 Notebook.prototype.get_notebook_name = function () {
1478 1493 return this.notebook_name;
1479 1494 };
1480 1495
1481 1496 /**
1482 1497 * Setter method for this notebook's name.
1483 1498 *
1484 1499 * @method set_notebook_name
1485 1500 * @param {String} name A new name for this notebook
1486 1501 */
1487 1502 Notebook.prototype.set_notebook_name = function (name) {
1488 1503 this.notebook_name = name;
1489 1504 };
1490 1505
1491 1506 /**
1492 1507 * Check that a notebook's name is valid.
1493 1508 *
1494 1509 * @method test_notebook_name
1495 1510 * @param {String} nbname A name for this notebook
1496 1511 * @return {Boolean} True if the name is valid, false if invalid
1497 1512 */
1498 1513 Notebook.prototype.test_notebook_name = function (nbname) {
1499 1514 nbname = nbname || '';
1500 1515 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1501 1516 return true;
1502 1517 } else {
1503 1518 return false;
1504 1519 };
1505 1520 };
1506 1521
1507 1522 /**
1508 1523 * Load a notebook from JSON (.ipynb).
1509 1524 *
1510 1525 * This currently handles one worksheet: others are deleted.
1511 1526 *
1512 1527 * @method fromJSON
1513 1528 * @param {Object} data JSON representation of a notebook
1514 1529 */
1515 1530 Notebook.prototype.fromJSON = function (data) {
1516 1531 var ncells = this.ncells();
1517 1532 var i;
1518 1533 for (i=0; i<ncells; i++) {
1519 1534 // Always delete cell 0 as they get renumbered as they are deleted.
1520 1535 this.delete_cell(0);
1521 1536 };
1522 1537 // Save the metadata and name.
1523 1538 this.metadata = data.metadata;
1524 1539 this.notebook_name = data.metadata.name;
1525 1540 // Only handle 1 worksheet for now.
1526 1541 var worksheet = data.worksheets[0];
1527 1542 if (worksheet !== undefined) {
1528 1543 if (worksheet.metadata) {
1529 1544 this.worksheet_metadata = worksheet.metadata;
1530 1545 }
1531 1546 var new_cells = worksheet.cells;
1532 1547 ncells = new_cells.length;
1533 1548 var cell_data = null;
1534 1549 var new_cell = null;
1535 1550 for (i=0; i<ncells; i++) {
1536 1551 cell_data = new_cells[i];
1537 1552 // VERSIONHACK: plaintext -> raw
1538 1553 // handle never-released plaintext name for raw cells
1539 1554 if (cell_data.cell_type === 'plaintext'){
1540 1555 cell_data.cell_type = 'raw';
1541 1556 }
1542 1557
1543 1558 new_cell = this.insert_cell_below(cell_data.cell_type);
1544 1559 new_cell.fromJSON(cell_data);
1545 1560 };
1546 1561 };
1547 1562 if (data.worksheets.length > 1) {
1548 1563 IPython.dialog.modal({
1549 1564 title : "Multiple worksheets",
1550 1565 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1551 1566 "but this version of IPython can only handle the first. " +
1552 1567 "If you save this notebook, worksheets after the first will be lost.",
1553 1568 buttons : {
1554 1569 OK : {
1555 1570 class : "btn-danger"
1556 1571 }
1557 1572 }
1558 1573 });
1559 1574 }
1560 1575 };
1561 1576
1562 1577 /**
1563 1578 * Dump this notebook into a JSON-friendly object.
1564 1579 *
1565 1580 * @method toJSON
1566 1581 * @return {Object} A JSON-friendly representation of this notebook.
1567 1582 */
1568 1583 Notebook.prototype.toJSON = function () {
1569 1584 var cells = this.get_cells();
1570 1585 var ncells = cells.length;
1571 1586 var cell_array = new Array(ncells);
1572 1587 for (var i=0; i<ncells; i++) {
1573 1588 cell_array[i] = cells[i].toJSON();
1574 1589 };
1575 1590 var data = {
1576 1591 // Only handle 1 worksheet for now.
1577 1592 worksheets : [{
1578 1593 cells: cell_array,
1579 1594 metadata: this.worksheet_metadata
1580 1595 }],
1581 1596 metadata : this.metadata
1582 1597 };
1583 1598 return data;
1584 1599 };
1585 1600
1586 1601 /**
1587 1602 * Start an autosave timer, for periodically saving the notebook.
1588 1603 *
1589 1604 * @method set_autosave_interval
1590 1605 * @param {Integer} interval the autosave interval in milliseconds
1591 1606 */
1592 1607 Notebook.prototype.set_autosave_interval = function (interval) {
1593 1608 var that = this;
1594 1609 // clear previous interval, so we don't get simultaneous timers
1595 1610 if (this.autosave_timer) {
1596 1611 clearInterval(this.autosave_timer);
1597 1612 }
1598 1613
1599 1614 this.autosave_interval = this.minimum_autosave_interval = interval;
1600 1615 if (interval) {
1601 1616 this.autosave_timer = setInterval(function() {
1602 1617 if (that.dirty) {
1603 1618 that.save_notebook();
1604 1619 }
1605 1620 }, interval);
1606 1621 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1607 1622 } else {
1608 1623 this.autosave_timer = null;
1609 1624 $([IPython.events]).trigger("autosave_disabled.Notebook");
1610 1625 };
1611 1626 };
1612 1627
1613 1628 /**
1614 1629 * Save this notebook on the server.
1615 1630 *
1616 1631 * @method save_notebook
1617 1632 */
1618 1633 Notebook.prototype.save_notebook = function () {
1619 1634 // We may want to move the name/id/nbformat logic inside toJSON?
1620 1635 var data = this.toJSON();
1621 1636 data.metadata.name = this.notebook_name;
1622 1637 data.nbformat = this.nbformat;
1623 1638 data.nbformat_minor = this.nbformat_minor;
1624 1639
1625 1640 // time the ajax call for autosave tuning purposes.
1626 1641 var start = new Date().getTime();
1627 1642
1628 1643 // We do the call with settings so we can set cache to false.
1629 1644 var settings = {
1630 1645 processData : false,
1631 1646 cache : false,
1632 1647 type : "PUT",
1633 1648 data : JSON.stringify(data),
1634 1649 headers : {'Content-Type': 'application/json'},
1635 1650 success : $.proxy(this.save_notebook_success, this, start),
1636 1651 error : $.proxy(this.save_notebook_error, this)
1637 1652 };
1638 1653 $([IPython.events]).trigger('notebook_saving.Notebook');
1639 1654 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1640 1655 $.ajax(url, settings);
1641 1656 };
1642 1657
1643 1658 /**
1644 1659 * Success callback for saving a notebook.
1645 1660 *
1646 1661 * @method save_notebook_success
1647 1662 * @param {Integer} start the time when the save request started
1648 1663 * @param {Object} data JSON representation of a notebook
1649 1664 * @param {String} status Description of response status
1650 1665 * @param {jqXHR} xhr jQuery Ajax object
1651 1666 */
1652 1667 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1653 1668 this.set_dirty(false);
1654 1669 $([IPython.events]).trigger('notebook_saved.Notebook');
1655 1670 this._update_autosave_interval(start);
1656 1671 if (this._checkpoint_after_save) {
1657 1672 this.create_checkpoint();
1658 1673 this._checkpoint_after_save = false;
1659 1674 };
1660 1675 };
1661 1676
1662 1677 /**
1663 1678 * update the autosave interval based on how long the last save took
1664 1679 *
1665 1680 * @method _update_autosave_interval
1666 1681 * @param {Integer} timestamp when the save request started
1667 1682 */
1668 1683 Notebook.prototype._update_autosave_interval = function (start) {
1669 1684 var duration = (new Date().getTime() - start);
1670 1685 if (this.autosave_interval) {
1671 1686 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1672 1687 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1673 1688 // round to 10 seconds, otherwise we will be setting a new interval too often
1674 1689 interval = 10000 * Math.round(interval / 10000);
1675 1690 // set new interval, if it's changed
1676 1691 if (interval != this.autosave_interval) {
1677 1692 this.set_autosave_interval(interval);
1678 1693 }
1679 1694 }
1680 1695 };
1681 1696
1682 1697 /**
1683 1698 * Failure callback for saving a notebook.
1684 1699 *
1685 1700 * @method save_notebook_error
1686 1701 * @param {jqXHR} xhr jQuery Ajax object
1687 1702 * @param {String} status Description of response status
1688 1703 * @param {String} error_msg HTTP error message
1689 1704 */
1690 1705 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1691 1706 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1692 1707 };
1693 1708
1694 1709 /**
1695 1710 * Request a notebook's data from the server.
1696 1711 *
1697 1712 * @method load_notebook
1698 1713 * @param {String} notebook_id A notebook to load
1699 1714 */
1700 1715 Notebook.prototype.load_notebook = function (notebook_id) {
1701 1716 var that = this;
1702 1717 this.notebook_id = notebook_id;
1703 1718 // We do the call with settings so we can set cache to false.
1704 1719 var settings = {
1705 1720 processData : false,
1706 1721 cache : false,
1707 1722 type : "GET",
1708 1723 dataType : "json",
1709 1724 success : $.proxy(this.load_notebook_success,this),
1710 1725 error : $.proxy(this.load_notebook_error,this),
1711 1726 };
1712 1727 $([IPython.events]).trigger('notebook_loading.Notebook');
1713 1728 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1714 1729 $.ajax(url, settings);
1715 1730 };
1716 1731
1717 1732 /**
1718 1733 * Success callback for loading a notebook from the server.
1719 1734 *
1720 1735 * Load notebook data from the JSON response.
1721 1736 *
1722 1737 * @method load_notebook_success
1723 1738 * @param {Object} data JSON representation of a notebook
1724 1739 * @param {String} status Description of response status
1725 1740 * @param {jqXHR} xhr jQuery Ajax object
1726 1741 */
1727 1742 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1728 1743 this.fromJSON(data);
1729 1744 if (this.ncells() === 0) {
1730 1745 this.insert_cell_below('code');
1731 1746 };
1732 1747 this.set_dirty(false);
1733 1748 this.select(0);
1734 1749 this.scroll_to_top();
1735 1750 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1736 1751 var msg = "This notebook has been converted from an older " +
1737 1752 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1738 1753 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1739 1754 "newer notebook format will be used and older versions of IPython " +
1740 1755 "may not be able to read it. To keep the older version, close the " +
1741 1756 "notebook without saving it.";
1742 1757 IPython.dialog.modal({
1743 1758 title : "Notebook converted",
1744 1759 body : msg,
1745 1760 buttons : {
1746 1761 OK : {
1747 1762 class : "btn-primary"
1748 1763 }
1749 1764 }
1750 1765 });
1751 1766 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1752 1767 var that = this;
1753 1768 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1754 1769 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1755 1770 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1756 1771 this_vs + ". You can still work with this notebook, but some features " +
1757 1772 "introduced in later notebook versions may not be available."
1758 1773
1759 1774 IPython.dialog.modal({
1760 1775 title : "Newer Notebook",
1761 1776 body : msg,
1762 1777 buttons : {
1763 1778 OK : {
1764 1779 class : "btn-danger"
1765 1780 }
1766 1781 }
1767 1782 });
1768 1783
1769 1784 }
1770 1785
1771 1786 // Create the kernel after the notebook is completely loaded to prevent
1772 1787 // code execution upon loading, which is a security risk.
1773 1788 if (! this.read_only) {
1774 1789 this.start_kernel();
1775 1790 // load our checkpoint list
1776 1791 IPython.notebook.list_checkpoints();
1777 1792 }
1778 1793 $([IPython.events]).trigger('notebook_loaded.Notebook');
1779 1794 };
1780 1795
1781 1796 /**
1782 1797 * Failure callback for loading a notebook from the server.
1783 1798 *
1784 1799 * @method load_notebook_error
1785 1800 * @param {jqXHR} xhr jQuery Ajax object
1786 1801 * @param {String} textStatus Description of response status
1787 1802 * @param {String} errorThrow HTTP error message
1788 1803 */
1789 1804 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1790 1805 if (xhr.status === 500) {
1791 1806 var msg = "An error occurred while loading this notebook. Most likely " +
1792 1807 "this notebook is in a newer format than is supported by this " +
1793 1808 "version of IPython. This version can load notebook formats " +
1794 1809 "v"+this.nbformat+" or earlier.";
1795 1810
1796 1811 IPython.dialog.modal({
1797 1812 title: "Error loading notebook",
1798 1813 body : msg,
1799 1814 buttons : {
1800 1815 "OK": {}
1801 1816 }
1802 1817 });
1803 1818 }
1804 1819 }
1805 1820
1806 1821 /********************* checkpoint-related *********************/
1807 1822
1808 1823 /**
1809 1824 * Save the notebook then immediately create a checkpoint.
1810 1825 *
1811 1826 * @method save_checkpoint
1812 1827 */
1813 1828 Notebook.prototype.save_checkpoint = function () {
1814 1829 this._checkpoint_after_save = true;
1815 1830 this.save_notebook();
1816 1831 };
1817 1832
1818 1833 /**
1819 1834 * List checkpoints for this notebook.
1820 1835 *
1821 1836 * @method list_checkpoint
1822 1837 */
1823 1838 Notebook.prototype.list_checkpoints = function () {
1824 1839 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1825 1840 $.get(url).done(
1826 1841 $.proxy(this.list_checkpoints_success, this)
1827 1842 ).fail(
1828 1843 $.proxy(this.list_checkpoints_error, this)
1829 1844 );
1830 1845 };
1831 1846
1832 1847 /**
1833 1848 * Success callback for listing checkpoints.
1834 1849 *
1835 1850 * @method list_checkpoint_success
1836 1851 * @param {Object} data JSON representation of a checkpoint
1837 1852 * @param {String} status Description of response status
1838 1853 * @param {jqXHR} xhr jQuery Ajax object
1839 1854 */
1840 1855 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
1841 1856 var data = $.parseJSON(data);
1842 1857 if (data.length) {
1843 1858 this.last_checkpoint = data[0];
1844 1859 } else {
1845 1860 this.last_checkpoint = null;
1846 1861 }
1847 1862 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
1848 1863 };
1849 1864
1850 1865 /**
1851 1866 * Failure callback for listing a checkpoint.
1852 1867 *
1853 1868 * @method list_checkpoint_error
1854 1869 * @param {jqXHR} xhr jQuery Ajax object
1855 1870 * @param {String} status Description of response status
1856 1871 * @param {String} error_msg HTTP error message
1857 1872 */
1858 1873 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
1859 1874 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
1860 1875 };
1861 1876
1862 1877 /**
1863 1878 * Create a checkpoint of this notebook on the server from the most recent save.
1864 1879 *
1865 1880 * @method create_checkpoint
1866 1881 */
1867 1882 Notebook.prototype.create_checkpoint = function () {
1868 1883 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints';
1869 1884 $.post(url).done(
1870 1885 $.proxy(this.create_checkpoint_success, this)
1871 1886 ).fail(
1872 1887 $.proxy(this.create_checkpoint_error, this)
1873 1888 );
1874 1889 };
1875 1890
1876 1891 /**
1877 1892 * Success callback for creating a checkpoint.
1878 1893 *
1879 1894 * @method create_checkpoint_success
1880 1895 * @param {Object} data JSON representation of a checkpoint
1881 1896 * @param {String} status Description of response status
1882 1897 * @param {jqXHR} xhr jQuery Ajax object
1883 1898 */
1884 1899 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
1885 1900 var data = $.parseJSON(data);
1886 1901 this.last_checkpoint = data;
1887 1902 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
1888 1903 };
1889 1904
1890 1905 /**
1891 1906 * Failure callback for creating a checkpoint.
1892 1907 *
1893 1908 * @method create_checkpoint_error
1894 1909 * @param {jqXHR} xhr jQuery Ajax object
1895 1910 * @param {String} status Description of response status
1896 1911 * @param {String} error_msg HTTP error message
1897 1912 */
1898 1913 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
1899 1914 $([IPython.events]).trigger('checkpoint_failed.Notebook');
1900 1915 };
1901 1916
1902 1917 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
1903 1918 var that = this;
1904 1919 var checkpoint = checkpoint || this.last_checkpoint;
1905 1920 if ( ! checkpoint ) {
1906 1921 console.log("restore dialog, but no checkpoint to restore to!");
1907 1922 return;
1908 1923 }
1909 1924 var body = $('<div/>').append(
1910 1925 $('<p/>').addClass("p-space").text(
1911 1926 "Are you sure you want to revert the notebook to " +
1912 1927 "the latest checkpoint?"
1913 1928 ).append(
1914 1929 $("<strong/>").text(
1915 1930 " This cannot be undone."
1916 1931 )
1917 1932 )
1918 1933 ).append(
1919 1934 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
1920 1935 ).append(
1921 1936 $('<p/>').addClass("p-space").text(
1922 1937 Date(checkpoint.last_modified)
1923 1938 ).css("text-align", "center")
1924 1939 );
1925 1940
1926 1941 IPython.dialog.modal({
1927 1942 title : "Revert notebook to checkpoint",
1928 1943 body : body,
1929 1944 buttons : {
1930 1945 Revert : {
1931 1946 class : "btn-danger",
1932 1947 click : function () {
1933 1948 that.restore_checkpoint(checkpoint.checkpoint_id);
1934 1949 }
1935 1950 },
1936 1951 Cancel : {}
1937 1952 }
1938 1953 });
1939 1954 }
1940 1955
1941 1956 /**
1942 1957 * Restore the notebook to a checkpoint state.
1943 1958 *
1944 1959 * @method restore_checkpoint
1945 1960 * @param {String} checkpoint ID
1946 1961 */
1947 1962 Notebook.prototype.restore_checkpoint = function (checkpoint) {
1948 1963 $([IPython.events]).trigger('checkpoint_restoring.Notebook', checkpoint);
1949 1964 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1950 1965 $.post(url).done(
1951 1966 $.proxy(this.restore_checkpoint_success, this)
1952 1967 ).fail(
1953 1968 $.proxy(this.restore_checkpoint_error, this)
1954 1969 );
1955 1970 };
1956 1971
1957 1972 /**
1958 1973 * Success callback for restoring a notebook to a checkpoint.
1959 1974 *
1960 1975 * @method restore_checkpoint_success
1961 1976 * @param {Object} data (ignored, should be empty)
1962 1977 * @param {String} status Description of response status
1963 1978 * @param {jqXHR} xhr jQuery Ajax object
1964 1979 */
1965 1980 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
1966 1981 $([IPython.events]).trigger('checkpoint_restored.Notebook');
1967 1982 this.load_notebook(this.notebook_id);
1968 1983 };
1969 1984
1970 1985 /**
1971 1986 * Failure callback for restoring a notebook to a checkpoint.
1972 1987 *
1973 1988 * @method restore_checkpoint_error
1974 1989 * @param {jqXHR} xhr jQuery Ajax object
1975 1990 * @param {String} status Description of response status
1976 1991 * @param {String} error_msg HTTP error message
1977 1992 */
1978 1993 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
1979 1994 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
1980 1995 };
1981 1996
1982 1997 /**
1983 1998 * Delete a notebook checkpoint.
1984 1999 *
1985 2000 * @method delete_checkpoint
1986 2001 * @param {String} checkpoint ID
1987 2002 */
1988 2003 Notebook.prototype.delete_checkpoint = function (checkpoint) {
1989 2004 $([IPython.events]).trigger('checkpoint_deleting.Notebook', checkpoint);
1990 2005 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id + '/checkpoints/' + checkpoint;
1991 2006 $.ajax(url, {
1992 2007 type: 'DELETE',
1993 2008 success: $.proxy(this.delete_checkpoint_success, this),
1994 2009 error: $.proxy(this.delete_notebook_error,this)
1995 2010 });
1996 2011 };
1997 2012
1998 2013 /**
1999 2014 * Success callback for deleting a notebook checkpoint
2000 2015 *
2001 2016 * @method delete_checkpoint_success
2002 2017 * @param {Object} data (ignored, should be empty)
2003 2018 * @param {String} status Description of response status
2004 2019 * @param {jqXHR} xhr jQuery Ajax object
2005 2020 */
2006 2021 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2007 2022 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2008 2023 this.load_notebook(this.notebook_id);
2009 2024 };
2010 2025
2011 2026 /**
2012 2027 * Failure callback for deleting a notebook checkpoint.
2013 2028 *
2014 2029 * @method delete_checkpoint_error
2015 2030 * @param {jqXHR} xhr jQuery Ajax object
2016 2031 * @param {String} status Description of response status
2017 2032 * @param {String} error_msg HTTP error message
2018 2033 */
2019 2034 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2020 2035 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2021 2036 };
2022 2037
2023 2038
2024 2039 IPython.Notebook = Notebook;
2025 2040
2026 2041
2027 2042 return IPython;
2028 2043
2029 2044 }(IPython));
2030 2045
@@ -1,74 +1,77 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // QuickHelp button
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var QuickHelp = function (selector) {
15 15 };
16 16
17 17 QuickHelp.prototype.show_keyboard_shortcuts = function () {
18 18 // toggles display of keyboard shortcut dialog
19 19 var that = this;
20 20 if ( this.shortcut_dialog ){
21 21 // if dialog is already shown, close it
22 22 $(this.shortcut_dialog).modal("toggle");
23 23 return;
24 24 }
25 25 var body = $('<div/>');
26 26 var shortcuts = [
27 27 {key: 'Shift-Enter', help: 'run cell'},
28 28 {key: 'Ctrl-Enter', help: 'run cell in-place'},
29 29 {key: 'Alt-Enter', help: 'run cell, insert below'},
30 30 {key: 'Ctrl-m x', help: 'cut cell'},
31 31 {key: 'Ctrl-m c', help: 'copy cell'},
32 32 {key: 'Ctrl-m v', help: 'paste cell'},
33 33 {key: 'Ctrl-m d', help: 'delete cell'},
34 34 {key: 'Ctrl-m z', help: 'undo last cell deletion'},
35 35 {key: 'Ctrl-m a', help: 'insert cell above'},
36 36 {key: 'Ctrl-m b', help: 'insert cell below'},
37 37 {key: 'Ctrl-m o', help: 'toggle output'},
38 38 {key: 'Ctrl-m O', help: 'toggle output scroll'},
39 39 {key: 'Ctrl-m l', help: 'toggle line numbers'},
40 40 {key: 'Ctrl-m s', help: 'save notebook'},
41 41 {key: 'Ctrl-m j', help: 'move cell down'},
42 42 {key: 'Ctrl-m k', help: 'move cell up'},
43 43 {key: 'Ctrl-m y', help: 'code cell'},
44 44 {key: 'Ctrl-m m', help: 'markdown cell'},
45 45 {key: 'Ctrl-m t', help: 'raw cell'},
46 46 {key: 'Ctrl-m 1-6', help: 'heading 1-6 cell'},
47 {key: 'Ctrl-m 7', help: 'split cell'},
48 {key: 'Ctrl-m 8', help: 'merge cell above'},
49 {key: 'Ctrl-m 9', help: 'merge cell below'},
47 50 {key: 'Ctrl-m p', help: 'select previous'},
48 51 {key: 'Ctrl-m n', help: 'select next'},
49 52 {key: 'Ctrl-m i', help: 'interrupt kernel'},
50 53 {key: 'Ctrl-m .', help: 'restart kernel'},
51 54 {key: 'Ctrl-m h', help: 'show keyboard shortcuts'}
52 55 ];
53 56 for (var i=0; i<shortcuts.length; i++) {
54 57 body.append($('<div>').
55 58 append($('<span/>').addClass('shortcut_key').html(shortcuts[i].key)).
56 59 append($('<span/>').addClass('shortcut_descr').html(' : ' + shortcuts[i].help))
57 60 );
58 61 };
59 62 this.shortcut_dialog = IPython.dialog.modal({
60 63 title : "Keyboard shortcuts",
61 64 body : body,
62 65 destroy : false,
63 66 buttons : {
64 67 Close : {}
65 68 }
66 69 });
67 70 };
68 71
69 72 // Set module variables
70 73 IPython.QuickHelp = QuickHelp;
71 74
72 75 return IPython;
73 76
74 77 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now