##// END OF EJS Templates
Merge pull request #3221 from dwyde/remove-html-cell...
Min RK -
r10377:46c67bde merge
parent child Browse files
Show More
@@ -1,1782 +1,1740
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.dirty = false;
41 41 this.metadata = {};
42 42 // single worksheet for now
43 43 this.worksheet_metadata = {};
44 44 this.control_key_active = false;
45 45 this.notebook_id = null;
46 46 this.notebook_name = null;
47 47 this.notebook_name_blacklist_re = /[\/\\:]/;
48 48 this.nbformat = 3 // Increment this when changing the nbformat
49 49 this.nbformat_minor = 0 // Increment this when changing the nbformat
50 50 this.style();
51 51 this.create_elements();
52 52 this.bind_events();
53 53 };
54 54
55 55 /**
56 56 * Tweak the notebook's CSS style.
57 57 *
58 58 * @method style
59 59 */
60 60 Notebook.prototype.style = function () {
61 61 $('div#notebook').addClass('border-box-sizing');
62 62 };
63 63
64 64 /**
65 65 * Get the root URL of the notebook server.
66 66 *
67 67 * @method baseProjectUrl
68 68 * @return {String} The base project URL
69 69 */
70 70 Notebook.prototype.baseProjectUrl = function(){
71 71 return this._baseProjectUrl || $('body').data('baseProjectUrl');
72 72 };
73 73
74 74 /**
75 75 * Create an HTML and CSS representation of the notebook.
76 76 *
77 77 * @method create_elements
78 78 */
79 79 Notebook.prototype.create_elements = function () {
80 80 // We add this end_space div to the end of the notebook div to:
81 81 // i) provide a margin between the last cell and the end of the notebook
82 82 // ii) to prevent the div from scrolling up when the last cell is being
83 83 // edited, but is too low on the page, which browsers will do automatically.
84 84 var that = this;
85 85 var end_space = $('<div/>').addClass('end_space').height("30%");
86 86 end_space.dblclick(function (e) {
87 87 if (that.read_only) return;
88 88 var ncells = that.ncells();
89 89 that.insert_cell_below('code',ncells-1);
90 90 });
91 91 this.element.append(end_space);
92 92 $('div#notebook').addClass('border-box-sizing');
93 93 };
94 94
95 95 /**
96 96 * Bind JavaScript events: key presses and custom IPython events.
97 97 *
98 98 * @method bind_events
99 99 */
100 100 Notebook.prototype.bind_events = function () {
101 101 var that = this;
102 102
103 103 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
104 104 var index = that.find_cell_index(data.cell);
105 105 var new_cell = that.insert_cell_below('code',index);
106 106 new_cell.set_text(data.text);
107 107 that.dirty = true;
108 108 });
109 109
110 110 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
111 111 that.dirty = data.value;
112 112 });
113 113
114 114 $([IPython.events]).on('select.Cell', function (event, data) {
115 115 var index = that.find_cell_index(data.cell);
116 116 that.select(index);
117 117 });
118 118
119 119
120 120 $(document).keydown(function (event) {
121 121 // console.log(event);
122 122 if (that.read_only) return true;
123 123
124 124 // Save (CTRL+S) or (AppleKey+S)
125 125 //metaKey = applekey on mac
126 126 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
127 127 that.save_notebook();
128 128 event.preventDefault();
129 129 return false;
130 130 } else if (event.which === key.ESC) {
131 131 // Intercept escape at highest level to avoid closing
132 132 // websocket connection with firefox
133 133 event.preventDefault();
134 134 } else if (event.which === key.SHIFT) {
135 135 // ignore shift keydown
136 136 return true;
137 137 }
138 138 if (event.which === key.UPARROW && !event.shiftKey) {
139 139 var cell = that.get_selected_cell();
140 140 if (cell && cell.at_top()) {
141 141 event.preventDefault();
142 142 that.select_prev();
143 143 };
144 144 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
145 145 var cell = that.get_selected_cell();
146 146 if (cell && cell.at_bottom()) {
147 147 event.preventDefault();
148 148 that.select_next();
149 149 };
150 150 } else if (event.which === key.ENTER && event.shiftKey) {
151 151 that.execute_selected_cell();
152 152 return false;
153 153 } else if (event.which === key.ENTER && event.altKey) {
154 154 // Execute code cell, and insert new in place
155 155 that.execute_selected_cell();
156 156 // Only insert a new cell, if we ended up in an already populated cell
157 157 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
158 158 that.insert_cell_above('code');
159 159 }
160 160 return false;
161 161 } else if (event.which === key.ENTER && event.ctrlKey) {
162 162 that.execute_selected_cell({terminal:true});
163 163 return false;
164 164 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
165 165 that.control_key_active = true;
166 166 return false;
167 167 } else if (event.which === 88 && that.control_key_active) {
168 168 // Cut selected cell = x
169 169 that.cut_cell();
170 170 that.control_key_active = false;
171 171 return false;
172 172 } else if (event.which === 67 && that.control_key_active) {
173 173 // Copy selected cell = c
174 174 that.copy_cell();
175 175 that.control_key_active = false;
176 176 return false;
177 177 } else if (event.which === 86 && that.control_key_active) {
178 178 // Paste below selected cell = v
179 179 that.paste_cell_below();
180 180 that.control_key_active = false;
181 181 return false;
182 182 } else if (event.which === 68 && that.control_key_active) {
183 183 // Delete selected cell = d
184 184 that.delete_cell();
185 185 that.control_key_active = false;
186 186 return false;
187 187 } else if (event.which === 65 && that.control_key_active) {
188 188 // Insert code cell above selected = a
189 189 that.insert_cell_above('code');
190 190 that.control_key_active = false;
191 191 return false;
192 192 } else if (event.which === 66 && that.control_key_active) {
193 193 // Insert code cell below selected = b
194 194 that.insert_cell_below('code');
195 195 that.control_key_active = false;
196 196 return false;
197 197 } else if (event.which === 89 && that.control_key_active) {
198 198 // To code = y
199 199 that.to_code();
200 200 that.control_key_active = false;
201 201 return false;
202 202 } else if (event.which === 77 && that.control_key_active) {
203 203 // To markdown = m
204 204 that.to_markdown();
205 205 that.control_key_active = false;
206 206 return false;
207 207 } else if (event.which === 84 && that.control_key_active) {
208 208 // To Raw = t
209 209 that.to_raw();
210 210 that.control_key_active = false;
211 211 return false;
212 212 } else if (event.which === 49 && that.control_key_active) {
213 213 // To Heading 1 = 1
214 214 that.to_heading(undefined, 1);
215 215 that.control_key_active = false;
216 216 return false;
217 217 } else if (event.which === 50 && that.control_key_active) {
218 218 // To Heading 2 = 2
219 219 that.to_heading(undefined, 2);
220 220 that.control_key_active = false;
221 221 return false;
222 222 } else if (event.which === 51 && that.control_key_active) {
223 223 // To Heading 3 = 3
224 224 that.to_heading(undefined, 3);
225 225 that.control_key_active = false;
226 226 return false;
227 227 } else if (event.which === 52 && that.control_key_active) {
228 228 // To Heading 4 = 4
229 229 that.to_heading(undefined, 4);
230 230 that.control_key_active = false;
231 231 return false;
232 232 } else if (event.which === 53 && that.control_key_active) {
233 233 // To Heading 5 = 5
234 234 that.to_heading(undefined, 5);
235 235 that.control_key_active = false;
236 236 return false;
237 237 } else if (event.which === 54 && that.control_key_active) {
238 238 // To Heading 6 = 6
239 239 that.to_heading(undefined, 6);
240 240 that.control_key_active = false;
241 241 return false;
242 242 } else if (event.which === 79 && that.control_key_active) {
243 243 // Toggle output = o
244 244 if (event.shiftKey){
245 245 that.toggle_output_scroll();
246 246 } else {
247 247 that.toggle_output();
248 248 }
249 249 that.control_key_active = false;
250 250 return false;
251 251 } else if (event.which === 83 && that.control_key_active) {
252 252 // Save notebook = s
253 253 that.save_notebook();
254 254 that.control_key_active = false;
255 255 return false;
256 256 } else if (event.which === 74 && that.control_key_active) {
257 257 // Move cell down = j
258 258 that.move_cell_down();
259 259 that.control_key_active = false;
260 260 return false;
261 261 } else if (event.which === 75 && that.control_key_active) {
262 262 // Move cell up = k
263 263 that.move_cell_up();
264 264 that.control_key_active = false;
265 265 return false;
266 266 } else if (event.which === 80 && that.control_key_active) {
267 267 // Select previous = p
268 268 that.select_prev();
269 269 that.control_key_active = false;
270 270 return false;
271 271 } else if (event.which === 78 && that.control_key_active) {
272 272 // Select next = n
273 273 that.select_next();
274 274 that.control_key_active = false;
275 275 return false;
276 276 } else if (event.which === 76 && that.control_key_active) {
277 277 // Toggle line numbers = l
278 278 that.cell_toggle_line_numbers();
279 279 that.control_key_active = false;
280 280 return false;
281 281 } else if (event.which === 73 && that.control_key_active) {
282 282 // Interrupt kernel = i
283 283 that.kernel.interrupt();
284 284 that.control_key_active = false;
285 285 return false;
286 286 } else if (event.which === 190 && that.control_key_active) {
287 287 // Restart kernel = . # matches qt console
288 288 that.restart_kernel();
289 289 that.control_key_active = false;
290 290 return false;
291 291 } else if (event.which === 72 && that.control_key_active) {
292 292 // Show keyboard shortcuts = h
293 293 IPython.quick_help.show_keyboard_shortcuts();
294 294 that.control_key_active = false;
295 295 return false;
296 296 } else if (event.which === 90 && that.control_key_active) {
297 297 // Undo last cell delete = z
298 298 that.undelete();
299 299 that.control_key_active = false;
300 300 return false;
301 301 } else if (that.control_key_active) {
302 302 that.control_key_active = false;
303 303 return true;
304 304 };
305 305 return true;
306 306 });
307 307
308 308 var collapse_time = function(time){
309 309 var app_height = $('#ipython-main-app').height(); // content height
310 310 var splitter_height = $('div#pager_splitter').outerHeight(true);
311 311 var new_height = app_height - splitter_height;
312 312 that.element.animate({height : new_height + 'px'}, time);
313 313 }
314 314
315 315 this.element.bind('collapse_pager', function (event,extrap) {
316 316 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
317 317 collapse_time(time);
318 318 });
319 319
320 320 var expand_time = function(time) {
321 321 var app_height = $('#ipython-main-app').height(); // content height
322 322 var splitter_height = $('div#pager_splitter').outerHeight(true);
323 323 var pager_height = $('div#pager').outerHeight(true);
324 324 var new_height = app_height - pager_height - splitter_height;
325 325 that.element.animate({height : new_height + 'px'}, time);
326 326 }
327 327
328 328 this.element.bind('expand_pager', function (event, extrap) {
329 329 var time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
330 330 expand_time(time);
331 331 });
332 332
333 333 $(window).bind('beforeunload', function () {
334 334 // TODO: Make killing the kernel configurable.
335 335 var kill_kernel = false;
336 336 if (kill_kernel) {
337 337 that.kernel.kill();
338 338 }
339 339 if (that.dirty && ! that.read_only) {
340 340 return "You have unsaved changes that will be lost if you leave this page.";
341 341 };
342 342 // Null is the *only* return value that will make the browser not
343 343 // pop up the "don't leave" dialog.
344 344 return null;
345 345 });
346 346 };
347 347
348 348 /**
349 349 * Scroll the top of the page to a given cell.
350 350 *
351 351 * @method scroll_to_cell
352 352 * @param {Number} cell_number An index of the cell to view
353 353 * @param {Number} time Animation time in milliseconds
354 354 * @return {Number} Pixel offset from the top of the container
355 355 */
356 356 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
357 357 var cells = this.get_cells();
358 358 var time = time || 0;
359 359 cell_number = Math.min(cells.length-1,cell_number);
360 360 cell_number = Math.max(0 ,cell_number);
361 361 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
362 362 this.element.animate({scrollTop:scroll_value}, time);
363 363 return scroll_value;
364 364 };
365 365
366 366 /**
367 367 * Scroll to the bottom of the page.
368 368 *
369 369 * @method scroll_to_bottom
370 370 */
371 371 Notebook.prototype.scroll_to_bottom = function () {
372 372 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
373 373 };
374 374
375 375 /**
376 376 * Scroll to the top of the page.
377 377 *
378 378 * @method scroll_to_top
379 379 */
380 380 Notebook.prototype.scroll_to_top = function () {
381 381 this.element.animate({scrollTop:0}, 0);
382 382 };
383 383
384 384
385 385 // Cell indexing, retrieval, etc.
386 386
387 387 /**
388 388 * Get all cell elements in the notebook.
389 389 *
390 390 * @method get_cell_elements
391 391 * @return {jQuery} A selector of all cell elements
392 392 */
393 393 Notebook.prototype.get_cell_elements = function () {
394 394 return this.element.children("div.cell");
395 395 };
396 396
397 397 /**
398 398 * Get a particular cell element.
399 399 *
400 400 * @method get_cell_element
401 401 * @param {Number} index An index of a cell to select
402 402 * @return {jQuery} A selector of the given cell.
403 403 */
404 404 Notebook.prototype.get_cell_element = function (index) {
405 405 var result = null;
406 406 var e = this.get_cell_elements().eq(index);
407 407 if (e.length !== 0) {
408 408 result = e;
409 409 }
410 410 return result;
411 411 };
412 412
413 413 /**
414 414 * Count the cells in this notebook.
415 415 *
416 416 * @method ncells
417 417 * @return {Number} The number of cells in this notebook
418 418 */
419 419 Notebook.prototype.ncells = function () {
420 420 return this.get_cell_elements().length;
421 421 };
422 422
423 423 /**
424 424 * Get all Cell objects in this notebook.
425 425 *
426 426 * @method get_cells
427 427 * @return {Array} This notebook's Cell objects
428 428 */
429 429 // TODO: we are often calling cells as cells()[i], which we should optimize
430 430 // to cells(i) or a new method.
431 431 Notebook.prototype.get_cells = function () {
432 432 return this.get_cell_elements().toArray().map(function (e) {
433 433 return $(e).data("cell");
434 434 });
435 435 };
436 436
437 437 /**
438 438 * Get a Cell object from this notebook.
439 439 *
440 440 * @method get_cell
441 441 * @param {Number} index An index of a cell to retrieve
442 442 * @return {Cell} A particular cell
443 443 */
444 444 Notebook.prototype.get_cell = function (index) {
445 445 var result = null;
446 446 var ce = this.get_cell_element(index);
447 447 if (ce !== null) {
448 448 result = ce.data('cell');
449 449 }
450 450 return result;
451 451 }
452 452
453 453 /**
454 454 * Get the cell below a given cell.
455 455 *
456 456 * @method get_next_cell
457 457 * @param {Cell} cell The provided cell
458 458 * @return {Cell} The next cell
459 459 */
460 460 Notebook.prototype.get_next_cell = function (cell) {
461 461 var result = null;
462 462 var index = this.find_cell_index(cell);
463 463 if (this.is_valid_cell_index(index+1)) {
464 464 result = this.get_cell(index+1);
465 465 }
466 466 return result;
467 467 }
468 468
469 469 /**
470 470 * Get the cell above a given cell.
471 471 *
472 472 * @method get_prev_cell
473 473 * @param {Cell} cell The provided cell
474 474 * @return {Cell} The previous cell
475 475 */
476 476 Notebook.prototype.get_prev_cell = function (cell) {
477 477 // TODO: off-by-one
478 478 // nb.get_prev_cell(nb.get_cell(1)) is null
479 479 var result = null;
480 480 var index = this.find_cell_index(cell);
481 481 if (index !== null && index > 1) {
482 482 result = this.get_cell(index-1);
483 483 }
484 484 return result;
485 485 }
486 486
487 487 /**
488 488 * Get the numeric index of a given cell.
489 489 *
490 490 * @method find_cell_index
491 491 * @param {Cell} cell The provided cell
492 492 * @return {Number} The cell's numeric index
493 493 */
494 494 Notebook.prototype.find_cell_index = function (cell) {
495 495 var result = null;
496 496 this.get_cell_elements().filter(function (index) {
497 497 if ($(this).data("cell") === cell) {
498 498 result = index;
499 499 };
500 500 });
501 501 return result;
502 502 };
503 503
504 504 /**
505 505 * Get a given index , or the selected index if none is provided.
506 506 *
507 507 * @method index_or_selected
508 508 * @param {Number} index A cell's index
509 509 * @return {Number} The given index, or selected index if none is provided.
510 510 */
511 511 Notebook.prototype.index_or_selected = function (index) {
512 512 var i;
513 513 if (index === undefined || index === null) {
514 514 i = this.get_selected_index();
515 515 if (i === null) {
516 516 i = 0;
517 517 }
518 518 } else {
519 519 i = index;
520 520 }
521 521 return i;
522 522 };
523 523
524 524 /**
525 525 * Get the currently selected cell.
526 526 * @method get_selected_cell
527 527 * @return {Cell} The selected cell
528 528 */
529 529 Notebook.prototype.get_selected_cell = function () {
530 530 var index = this.get_selected_index();
531 531 return this.get_cell(index);
532 532 };
533 533
534 534 /**
535 535 * Check whether a cell index is valid.
536 536 *
537 537 * @method is_valid_cell_index
538 538 * @param {Number} index A cell index
539 539 * @return True if the index is valid, false otherwise
540 540 */
541 541 Notebook.prototype.is_valid_cell_index = function (index) {
542 542 if (index !== null && index >= 0 && index < this.ncells()) {
543 543 return true;
544 544 } else {
545 545 return false;
546 546 };
547 547 }
548 548
549 549 /**
550 550 * Get the index of the currently selected cell.
551 551
552 552 * @method get_selected_index
553 553 * @return {Number} The selected cell's numeric index
554 554 */
555 555 Notebook.prototype.get_selected_index = function () {
556 556 var result = null;
557 557 this.get_cell_elements().filter(function (index) {
558 558 if ($(this).data("cell").selected === true) {
559 559 result = index;
560 560 };
561 561 });
562 562 return result;
563 563 };
564 564
565 565
566 566 // Cell selection.
567 567
568 568 /**
569 569 * Programmatically select a cell.
570 570 *
571 571 * @method select
572 572 * @param {Number} index A cell's index
573 573 * @return {Notebook} This notebook
574 574 */
575 575 Notebook.prototype.select = function (index) {
576 576 if (this.is_valid_cell_index(index)) {
577 577 var sindex = this.get_selected_index()
578 578 if (sindex !== null && index !== sindex) {
579 579 this.get_cell(sindex).unselect();
580 580 };
581 581 var cell = this.get_cell(index);
582 582 cell.select();
583 583 if (cell.cell_type === 'heading') {
584 584 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
585 585 {'cell_type':cell.cell_type,level:cell.level}
586 586 );
587 587 } else {
588 588 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
589 589 {'cell_type':cell.cell_type}
590 590 );
591 591 };
592 592 };
593 593 return this;
594 594 };
595 595
596 596 /**
597 597 * Programmatically select the next cell.
598 598 *
599 599 * @method select_next
600 600 * @return {Notebook} This notebook
601 601 */
602 602 Notebook.prototype.select_next = function () {
603 603 var index = this.get_selected_index();
604 604 this.select(index+1);
605 605 return this;
606 606 };
607 607
608 608 /**
609 609 * Programmatically select the previous cell.
610 610 *
611 611 * @method select_prev
612 612 * @return {Notebook} This notebook
613 613 */
614 614 Notebook.prototype.select_prev = function () {
615 615 var index = this.get_selected_index();
616 616 this.select(index-1);
617 617 return this;
618 618 };
619 619
620 620
621 621 // Cell movement
622 622
623 623 /**
624 624 * Move given (or selected) cell up and select it.
625 625 *
626 626 * @method move_cell_up
627 627 * @param [index] {integer} cell index
628 628 * @return {Notebook} This notebook
629 629 **/
630 630 Notebook.prototype.move_cell_up = function (index) {
631 631 var i = this.index_or_selected(index);
632 632 if (this.is_valid_cell_index(i) && i > 0) {
633 633 var pivot = this.get_cell_element(i-1);
634 634 var tomove = this.get_cell_element(i);
635 635 if (pivot !== null && tomove !== null) {
636 636 tomove.detach();
637 637 pivot.before(tomove);
638 638 this.select(i-1);
639 639 };
640 640 this.dirty = true;
641 641 };
642 642 return this;
643 643 };
644 644
645 645
646 646 /**
647 647 * Move given (or selected) cell down and select it
648 648 *
649 649 * @method move_cell_down
650 650 * @param [index] {integer} cell index
651 651 * @return {Notebook} This notebook
652 652 **/
653 653 Notebook.prototype.move_cell_down = function (index) {
654 654 var i = this.index_or_selected(index);
655 655 if ( this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
656 656 var pivot = this.get_cell_element(i+1);
657 657 var tomove = this.get_cell_element(i);
658 658 if (pivot !== null && tomove !== null) {
659 659 tomove.detach();
660 660 pivot.after(tomove);
661 661 this.select(i+1);
662 662 };
663 663 };
664 664 this.dirty = true;
665 665 return this;
666 666 };
667 667
668 668
669 669 // Insertion, deletion.
670 670
671 671 /**
672 672 * Delete a cell from the notebook.
673 673 *
674 674 * @method delete_cell
675 675 * @param [index] A cell's numeric index
676 676 * @return {Notebook} This notebook
677 677 */
678 678 Notebook.prototype.delete_cell = function (index) {
679 679 var i = this.index_or_selected(index);
680 680 var cell = this.get_selected_cell();
681 681 this.undelete_backup = cell.toJSON();
682 682 $('#undelete_cell').removeClass('ui-state-disabled');
683 683 if (this.is_valid_cell_index(i)) {
684 684 var ce = this.get_cell_element(i);
685 685 ce.remove();
686 686 if (i === (this.ncells())) {
687 687 this.select(i-1);
688 688 this.undelete_index = i - 1;
689 689 this.undelete_below = true;
690 690 } else {
691 691 this.select(i);
692 692 this.undelete_index = i;
693 693 this.undelete_below = false;
694 694 };
695 695 this.dirty = true;
696 696 };
697 697 return this;
698 698 };
699 699
700 700 /**
701 701 * Insert a cell so that after insertion the cell is at given index.
702 702 *
703 703 * Similar to insert_above, but index parameter is mandatory
704 704 *
705 705 * Index will be brought back into the accissible range [0,n]
706 706 *
707 707 * @method insert_cell_at_index
708 * @param type {string} in ['code','html','markdown','heading']
708 * @param type {string} in ['code','markdown','heading']
709 709 * @param [index] {int} a valid index where to inser cell
710 710 *
711 711 * @return cell {cell|null} created cell or null
712 712 **/
713 713 Notebook.prototype.insert_cell_at_index = function(type, index){
714 714
715 715 var ncells = this.ncells();
716 716 var index = Math.min(index,ncells);
717 717 index = Math.max(index,0);
718 718 var cell = null;
719 719
720 720 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
721 721 if (type === 'code') {
722 722 cell = new IPython.CodeCell(this.kernel);
723 723 cell.set_input_prompt();
724 724 } else if (type === 'markdown') {
725 725 cell = new IPython.MarkdownCell();
726 } else if (type === 'html') {
727 cell = new IPython.HTMLCell();
728 726 } else if (type === 'raw') {
729 727 cell = new IPython.RawCell();
730 728 } else if (type === 'heading') {
731 729 cell = new IPython.HeadingCell();
732 730 }
733 731
734 732 if(this._insert_element_at_index(cell.element,index)){
735 733 cell.render();
736 734 this.select(this.find_cell_index(cell));
737 735 this.dirty = true;
738 736 }
739 737 }
740 738 return cell;
741 739
742 740 };
743 741
744 742 /**
745 743 * Insert an element at given cell index.
746 744 *
747 745 * @method _insert_element_at_index
748 746 * @param element {dom element} a cell element
749 747 * @param [index] {int} a valid index where to inser cell
750 748 * @private
751 749 *
752 750 * return true if everything whent fine.
753 751 **/
754 752 Notebook.prototype._insert_element_at_index = function(element, index){
755 753 if (element === undefined){
756 754 return false;
757 755 }
758 756
759 757 var ncells = this.ncells();
760 758
761 759 if (ncells === 0) {
762 760 // special case append if empty
763 761 this.element.find('div.end_space').before(element);
764 762 } else if ( ncells === index ) {
765 763 // special case append it the end, but not empty
766 764 this.get_cell_element(index-1).after(element);
767 765 } else if (this.is_valid_cell_index(index)) {
768 766 // otherwise always somewhere to append to
769 767 this.get_cell_element(index).before(element);
770 768 } else {
771 769 return false;
772 770 }
773 771
774 772 if (this.undelete_index !== null && index <= this.undelete_index) {
775 773 this.undelete_index = this.undelete_index + 1;
776 774 this.dirty = true;
777 775 }
778 776 return true;
779 777 };
780 778
781 779 /**
782 780 * Insert a cell of given type above given index, or at top
783 781 * of notebook if index smaller than 0.
784 782 *
785 783 * default index value is the one of currently selected cell
786 784 *
787 785 * @method insert_cell_above
788 786 * @param type {string} cell type
789 787 * @param [index] {integer}
790 788 *
791 789 * @return handle to created cell or null
792 790 **/
793 791 Notebook.prototype.insert_cell_above = function (type, index) {
794 792 index = this.index_or_selected(index);
795 793 return this.insert_cell_at_index(type, index);
796 794 };
797 795
798 796 /**
799 797 * Insert a cell of given type below given index, or at bottom
800 798 * of notebook if index greater thatn number of cell
801 799 *
802 800 * default index value is the one of currently selected cell
803 801 *
804 802 * @method insert_cell_below
805 803 * @param type {string} cell type
806 804 * @param [index] {integer}
807 805 *
808 806 * @return handle to created cell or null
809 807 *
810 808 **/
811 809 Notebook.prototype.insert_cell_below = function (type, index) {
812 810 index = this.index_or_selected(index);
813 811 return this.insert_cell_at_index(type, index+1);
814 812 };
815 813
816 814
817 815 /**
818 816 * Insert cell at end of notebook
819 817 *
820 818 * @method insert_cell_at_bottom
821 819 * @param {String} type cell type
822 820 *
823 821 * @return the added cell; or null
824 822 **/
825 823 Notebook.prototype.insert_cell_at_bottom = function (type){
826 824 var len = this.ncells();
827 825 return this.insert_cell_below(type,len-1);
828 826 };
829 827
830 828 /**
831 829 * Turn a cell into a code cell.
832 830 *
833 831 * @method to_code
834 832 * @param {Number} [index] A cell's index
835 833 */
836 834 Notebook.prototype.to_code = function (index) {
837 835 var i = this.index_or_selected(index);
838 836 if (this.is_valid_cell_index(i)) {
839 837 var source_element = this.get_cell_element(i);
840 838 var source_cell = source_element.data("cell");
841 839 if (!(source_cell instanceof IPython.CodeCell)) {
842 840 var target_cell = this.insert_cell_below('code',i);
843 841 var text = source_cell.get_text();
844 842 if (text === source_cell.placeholder) {
845 843 text = '';
846 844 }
847 845 target_cell.set_text(text);
848 846 // make this value the starting point, so that we can only undo
849 847 // to this state, instead of a blank cell
850 848 target_cell.code_mirror.clearHistory();
851 849 source_element.remove();
852 850 this.dirty = true;
853 851 };
854 852 };
855 853 };
856 854
857 855 /**
858 856 * Turn a cell into a Markdown cell.
859 857 *
860 858 * @method to_markdown
861 859 * @param {Number} [index] A cell's index
862 860 */
863 861 Notebook.prototype.to_markdown = function (index) {
864 862 var i = this.index_or_selected(index);
865 863 if (this.is_valid_cell_index(i)) {
866 864 var source_element = this.get_cell_element(i);
867 865 var source_cell = source_element.data("cell");
868 866 if (!(source_cell instanceof IPython.MarkdownCell)) {
869 867 var target_cell = this.insert_cell_below('markdown',i);
870 868 var text = source_cell.get_text();
871 869 if (text === source_cell.placeholder) {
872 870 text = '';
873 871 };
874 872 // The edit must come before the set_text.
875 873 target_cell.edit();
876 874 target_cell.set_text(text);
877 875 // make this value the starting point, so that we can only undo
878 876 // to this state, instead of a blank cell
879 877 target_cell.code_mirror.clearHistory();
880 878 source_element.remove();
881 879 this.dirty = true;
882 880 };
883 881 };
884 882 };
885 883
886 884 /**
887 * Turn a cell into an HTML cell.
888 *
889 * @method to_html
890 * @param {Number} [index] A cell's index
891 */
892 Notebook.prototype.to_html = function (index) {
893 // TODO: remove? This is never called
894 var i = this.index_or_selected(index);
895 if (this.is_valid_cell_index(i)) {
896 var source_element = this.get_cell_element(i);
897 var source_cell = source_element.data("cell");
898 var target_cell = null;
899 if (!(source_cell instanceof IPython.HTMLCell)) {
900 target_cell = this.insert_cell_below('html',i);
901 var text = source_cell.get_text();
902 if (text === source_cell.placeholder) {
903 text = '';
904 };
905 // The edit must come before the set_text.
906 target_cell.edit();
907 target_cell.set_text(text);
908 // make this value the starting point, so that we can only undo
909 // to this state, instead of a blank cell
910 target_cell.code_mirror.clearHistory();
911 source_element.remove();
912 this.dirty = true;
913 };
914 };
915 };
916
917 /**
918 885 * Turn a cell into a raw text cell.
919 886 *
920 887 * @method to_raw
921 888 * @param {Number} [index] A cell's index
922 889 */
923 890 Notebook.prototype.to_raw = function (index) {
924 891 var i = this.index_or_selected(index);
925 892 if (this.is_valid_cell_index(i)) {
926 893 var source_element = this.get_cell_element(i);
927 894 var source_cell = source_element.data("cell");
928 895 var target_cell = null;
929 896 if (!(source_cell instanceof IPython.RawCell)) {
930 897 target_cell = this.insert_cell_below('raw',i);
931 898 var text = source_cell.get_text();
932 899 if (text === source_cell.placeholder) {
933 900 text = '';
934 901 };
935 902 // The edit must come before the set_text.
936 903 target_cell.edit();
937 904 target_cell.set_text(text);
938 905 // make this value the starting point, so that we can only undo
939 906 // to this state, instead of a blank cell
940 907 target_cell.code_mirror.clearHistory();
941 908 source_element.remove();
942 909 this.dirty = true;
943 910 };
944 911 };
945 912 };
946 913
947 914 /**
948 915 * Turn a cell into a heading cell.
949 916 *
950 917 * @method to_heading
951 918 * @param {Number} [index] A cell's index
952 919 * @param {Number} [level] A heading level (e.g., 1 becomes &lt;h1&gt;)
953 920 */
954 921 Notebook.prototype.to_heading = function (index, level) {
955 922 level = level || 1;
956 923 var i = this.index_or_selected(index);
957 924 if (this.is_valid_cell_index(i)) {
958 925 var source_element = this.get_cell_element(i);
959 926 var source_cell = source_element.data("cell");
960 927 var target_cell = null;
961 928 if (source_cell instanceof IPython.HeadingCell) {
962 929 source_cell.set_level(level);
963 930 } else {
964 931 target_cell = this.insert_cell_below('heading',i);
965 932 var text = source_cell.get_text();
966 933 if (text === source_cell.placeholder) {
967 934 text = '';
968 935 };
969 936 // The edit must come before the set_text.
970 937 target_cell.set_level(level);
971 938 target_cell.edit();
972 939 target_cell.set_text(text);
973 940 // make this value the starting point, so that we can only undo
974 941 // to this state, instead of a blank cell
975 942 target_cell.code_mirror.clearHistory();
976 943 source_element.remove();
977 944 this.dirty = true;
978 945 };
979 946 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
980 947 {'cell_type':'heading',level:level}
981 948 );
982 949 };
983 950 };
984 951
985 952
986 953 // Cut/Copy/Paste
987 954
988 955 /**
989 956 * Enable UI elements for pasting cells.
990 957 *
991 958 * @method enable_paste
992 959 */
993 960 Notebook.prototype.enable_paste = function () {
994 961 var that = this;
995 962 if (!this.paste_enabled) {
996 963 $('#paste_cell_replace').removeClass('ui-state-disabled')
997 964 .on('click', function () {that.paste_cell_replace();});
998 965 $('#paste_cell_above').removeClass('ui-state-disabled')
999 966 .on('click', function () {that.paste_cell_above();});
1000 967 $('#paste_cell_below').removeClass('ui-state-disabled')
1001 968 .on('click', function () {that.paste_cell_below();});
1002 969 this.paste_enabled = true;
1003 970 };
1004 971 };
1005 972
1006 973 /**
1007 974 * Disable UI elements for pasting cells.
1008 975 *
1009 976 * @method disable_paste
1010 977 */
1011 978 Notebook.prototype.disable_paste = function () {
1012 979 if (this.paste_enabled) {
1013 980 $('#paste_cell_replace').addClass('ui-state-disabled').off('click');
1014 981 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
1015 982 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
1016 983 this.paste_enabled = false;
1017 984 };
1018 985 };
1019 986
1020 987 /**
1021 988 * Cut a cell.
1022 989 *
1023 990 * @method cut_cell
1024 991 */
1025 992 Notebook.prototype.cut_cell = function () {
1026 993 this.copy_cell();
1027 994 this.delete_cell();
1028 995 }
1029 996
1030 997 /**
1031 998 * Copy a cell.
1032 999 *
1033 1000 * @method copy_cell
1034 1001 */
1035 1002 Notebook.prototype.copy_cell = function () {
1036 1003 var cell = this.get_selected_cell();
1037 1004 this.clipboard = cell.toJSON();
1038 1005 this.enable_paste();
1039 1006 };
1040 1007
1041 1008 /**
1042 1009 * Replace the selected cell with a cell in the clipboard.
1043 1010 *
1044 1011 * @method paste_cell_replace
1045 1012 */
1046 1013 Notebook.prototype.paste_cell_replace = function () {
1047 1014 if (this.clipboard !== null && this.paste_enabled) {
1048 1015 var cell_data = this.clipboard;
1049 1016 var new_cell = this.insert_cell_above(cell_data.cell_type);
1050 1017 new_cell.fromJSON(cell_data);
1051 1018 var old_cell = this.get_next_cell(new_cell);
1052 1019 this.delete_cell(this.find_cell_index(old_cell));
1053 1020 this.select(this.find_cell_index(new_cell));
1054 1021 };
1055 1022 };
1056 1023
1057 1024 /**
1058 1025 * Paste a cell from the clipboard above the selected cell.
1059 1026 *
1060 1027 * @method paste_cell_above
1061 1028 */
1062 1029 Notebook.prototype.paste_cell_above = function () {
1063 1030 if (this.clipboard !== null && this.paste_enabled) {
1064 1031 var cell_data = this.clipboard;
1065 1032 var new_cell = this.insert_cell_above(cell_data.cell_type);
1066 1033 new_cell.fromJSON(cell_data);
1067 1034 };
1068 1035 };
1069 1036
1070 1037 /**
1071 1038 * Paste a cell from the clipboard below the selected cell.
1072 1039 *
1073 1040 * @method paste_cell_below
1074 1041 */
1075 1042 Notebook.prototype.paste_cell_below = function () {
1076 1043 if (this.clipboard !== null && this.paste_enabled) {
1077 1044 var cell_data = this.clipboard;
1078 1045 var new_cell = this.insert_cell_below(cell_data.cell_type);
1079 1046 new_cell.fromJSON(cell_data);
1080 1047 };
1081 1048 };
1082 1049
1083 1050 // Cell undelete
1084 1051
1085 1052 /**
1086 1053 * Restore the most recently deleted cell.
1087 1054 *
1088 1055 * @method undelete
1089 1056 */
1090 1057 Notebook.prototype.undelete = function() {
1091 1058 if (this.undelete_backup !== null && this.undelete_index !== null) {
1092 1059 var current_index = this.get_selected_index();
1093 1060 if (this.undelete_index < current_index) {
1094 1061 current_index = current_index + 1;
1095 1062 }
1096 1063 if (this.undelete_index >= this.ncells()) {
1097 1064 this.select(this.ncells() - 1);
1098 1065 }
1099 1066 else {
1100 1067 this.select(this.undelete_index);
1101 1068 }
1102 1069 var cell_data = this.undelete_backup;
1103 1070 var new_cell = null;
1104 1071 if (this.undelete_below) {
1105 1072 new_cell = this.insert_cell_below(cell_data.cell_type);
1106 1073 } else {
1107 1074 new_cell = this.insert_cell_above(cell_data.cell_type);
1108 1075 }
1109 1076 new_cell.fromJSON(cell_data);
1110 1077 this.select(current_index);
1111 1078 this.undelete_backup = null;
1112 1079 this.undelete_index = null;
1113 1080 }
1114 1081 $('#undelete_cell').addClass('ui-state-disabled');
1115 1082 }
1116 1083
1117 1084 // Split/merge
1118 1085
1119 1086 /**
1120 1087 * Split the selected cell into two, at the cursor.
1121 1088 *
1122 1089 * @method split_cell
1123 1090 */
1124 1091 Notebook.prototype.split_cell = function () {
1125 1092 // Todo: implement spliting for other cell types.
1126 1093 var cell = this.get_selected_cell();
1127 1094 if (cell.is_splittable()) {
1128 1095 var texta = cell.get_pre_cursor();
1129 1096 var textb = cell.get_post_cursor();
1130 1097 if (cell instanceof IPython.CodeCell) {
1131 1098 cell.set_text(texta);
1132 1099 var new_cell = this.insert_cell_below('code');
1133 1100 new_cell.set_text(textb);
1134 1101 } else if (cell instanceof IPython.MarkdownCell) {
1135 1102 cell.set_text(texta);
1136 1103 cell.render();
1137 1104 var new_cell = this.insert_cell_below('markdown');
1138 1105 new_cell.edit(); // editor must be visible to call set_text
1139 1106 new_cell.set_text(textb);
1140 1107 new_cell.render();
1141 } else if (cell instanceof IPython.HTMLCell) {
1142 cell.set_text(texta);
1143 cell.render();
1144 var new_cell = this.insert_cell_below('html');
1145 new_cell.edit(); // editor must be visible to call set_text
1146 new_cell.set_text(textb);
1147 new_cell.render();
1148 };
1108 }
1149 1109 };
1150 1110 };
1151 1111
1152 1112 /**
1153 1113 * Combine the selected cell into the cell above it.
1154 1114 *
1155 1115 * @method merge_cell_above
1156 1116 */
1157 1117 Notebook.prototype.merge_cell_above = function () {
1158 1118 var index = this.get_selected_index();
1159 1119 var cell = this.get_cell(index);
1160 1120 if (index > 0) {
1161 1121 var upper_cell = this.get_cell(index-1);
1162 1122 var upper_text = upper_cell.get_text();
1163 1123 var text = cell.get_text();
1164 1124 if (cell instanceof IPython.CodeCell) {
1165 1125 cell.set_text(upper_text+'\n'+text);
1166 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
1126 } else if (cell instanceof IPython.MarkdownCell) {
1167 1127 cell.edit();
1168 1128 cell.set_text(upper_text+'\n'+text);
1169 1129 cell.render();
1170 1130 };
1171 1131 this.delete_cell(index-1);
1172 1132 this.select(this.find_cell_index(cell));
1173 1133 };
1174 1134 };
1175 1135
1176 1136 /**
1177 1137 * Combine the selected cell into the cell below it.
1178 1138 *
1179 1139 * @method merge_cell_below
1180 1140 */
1181 1141 Notebook.prototype.merge_cell_below = function () {
1182 1142 var index = this.get_selected_index();
1183 1143 var cell = this.get_cell(index);
1184 1144 if (index < this.ncells()-1) {
1185 1145 var lower_cell = this.get_cell(index+1);
1186 1146 var lower_text = lower_cell.get_text();
1187 1147 var text = cell.get_text();
1188 1148 if (cell instanceof IPython.CodeCell) {
1189 1149 cell.set_text(text+'\n'+lower_text);
1190 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
1150 } else if (cell instanceof IPython.MarkdownCell) {
1191 1151 cell.edit();
1192 1152 cell.set_text(text+'\n'+lower_text);
1193 1153 cell.render();
1194 1154 };
1195 1155 this.delete_cell(index+1);
1196 1156 this.select(this.find_cell_index(cell));
1197 1157 };
1198 1158 };
1199 1159
1200 1160
1201 1161 // Cell collapsing and output clearing
1202 1162
1203 1163 /**
1204 1164 * Hide a cell's output.
1205 1165 *
1206 1166 * @method collapse
1207 1167 * @param {Number} index A cell's numeric index
1208 1168 */
1209 1169 Notebook.prototype.collapse = function (index) {
1210 1170 var i = this.index_or_selected(index);
1211 1171 this.get_cell(i).collapse();
1212 1172 this.dirty = true;
1213 1173 };
1214 1174
1215 1175 /**
1216 1176 * Show a cell's output.
1217 1177 *
1218 1178 * @method expand
1219 1179 * @param {Number} index A cell's numeric index
1220 1180 */
1221 1181 Notebook.prototype.expand = function (index) {
1222 1182 var i = this.index_or_selected(index);
1223 1183 this.get_cell(i).expand();
1224 1184 this.dirty = true;
1225 1185 };
1226 1186
1227 1187 /** Toggle whether a cell's output is collapsed or expanded.
1228 1188 *
1229 1189 * @method toggle_output
1230 1190 * @param {Number} index A cell's numeric index
1231 1191 */
1232 1192 Notebook.prototype.toggle_output = function (index) {
1233 1193 var i = this.index_or_selected(index);
1234 1194 this.get_cell(i).toggle_output();
1235 1195 this.dirty = true;
1236 1196 };
1237 1197
1238 1198 /**
1239 1199 * Toggle a scrollbar for long cell outputs.
1240 1200 *
1241 1201 * @method toggle_output_scroll
1242 1202 * @param {Number} index A cell's numeric index
1243 1203 */
1244 1204 Notebook.prototype.toggle_output_scroll = function (index) {
1245 1205 var i = this.index_or_selected(index);
1246 1206 this.get_cell(i).toggle_output_scroll();
1247 1207 };
1248 1208
1249 1209 /**
1250 1210 * Hide each code cell's output area.
1251 1211 *
1252 1212 * @method collapse_all_output
1253 1213 */
1254 1214 Notebook.prototype.collapse_all_output = function () {
1255 1215 var ncells = this.ncells();
1256 1216 var cells = this.get_cells();
1257 1217 for (var i=0; i<ncells; i++) {
1258 1218 if (cells[i] instanceof IPython.CodeCell) {
1259 1219 cells[i].output_area.collapse();
1260 1220 }
1261 1221 };
1262 1222 // this should not be set if the `collapse` key is removed from nbformat
1263 1223 this.dirty = true;
1264 1224 };
1265 1225
1266 1226 /**
1267 1227 * Expand each code cell's output area, and add a scrollbar for long output.
1268 1228 *
1269 1229 * @method scroll_all_output
1270 1230 */
1271 1231 Notebook.prototype.scroll_all_output = function () {
1272 1232 var ncells = this.ncells();
1273 1233 var cells = this.get_cells();
1274 1234 for (var i=0; i<ncells; i++) {
1275 1235 if (cells[i] instanceof IPython.CodeCell) {
1276 1236 cells[i].output_area.expand();
1277 1237 cells[i].output_area.scroll_if_long(20);
1278 1238 }
1279 1239 };
1280 1240 // this should not be set if the `collapse` key is removed from nbformat
1281 1241 this.dirty = true;
1282 1242 };
1283 1243
1284 1244 /**
1285 1245 * Expand each code cell's output area, and remove scrollbars.
1286 1246 *
1287 1247 * @method expand_all_output
1288 1248 */
1289 1249 Notebook.prototype.expand_all_output = function () {
1290 1250 var ncells = this.ncells();
1291 1251 var cells = this.get_cells();
1292 1252 for (var i=0; i<ncells; i++) {
1293 1253 if (cells[i] instanceof IPython.CodeCell) {
1294 1254 cells[i].output_area.expand();
1295 1255 cells[i].output_area.unscroll_area();
1296 1256 }
1297 1257 };
1298 1258 // this should not be set if the `collapse` key is removed from nbformat
1299 1259 this.dirty = true;
1300 1260 };
1301 1261
1302 1262 /**
1303 1263 * Clear each code cell's output area.
1304 1264 *
1305 1265 * @method clear_all_output
1306 1266 */
1307 1267 Notebook.prototype.clear_all_output = function () {
1308 1268 var ncells = this.ncells();
1309 1269 var cells = this.get_cells();
1310 1270 for (var i=0; i<ncells; i++) {
1311 1271 if (cells[i] instanceof IPython.CodeCell) {
1312 1272 cells[i].clear_output(true,true,true);
1313 1273 // Make all In[] prompts blank, as well
1314 1274 // TODO: make this configurable (via checkbox?)
1315 1275 cells[i].set_input_prompt();
1316 1276 }
1317 1277 };
1318 1278 this.dirty = true;
1319 1279 };
1320 1280
1321 1281
1322 1282 // Other cell functions: line numbers, ...
1323 1283
1324 1284 /**
1325 1285 * Toggle line numbers in the selected cell's input area.
1326 1286 *
1327 1287 * @method cell_toggle_line_numbers
1328 1288 */
1329 1289 Notebook.prototype.cell_toggle_line_numbers = function() {
1330 1290 this.get_selected_cell().toggle_line_numbers();
1331 1291 };
1332 1292
1333 1293 // Kernel related things
1334 1294
1335 1295 /**
1336 1296 * Start a new kernel and set it on each code cell.
1337 1297 *
1338 1298 * @method start_kernel
1339 1299 */
1340 1300 Notebook.prototype.start_kernel = function () {
1341 1301 var base_url = $('body').data('baseKernelUrl') + "kernels";
1342 1302 this.kernel = new IPython.Kernel(base_url);
1343 1303 this.kernel.start(this.notebook_id);
1344 1304 // Now that the kernel has been created, tell the CodeCells about it.
1345 1305 var ncells = this.ncells();
1346 1306 for (var i=0; i<ncells; i++) {
1347 1307 var cell = this.get_cell(i);
1348 1308 if (cell instanceof IPython.CodeCell) {
1349 1309 cell.set_kernel(this.kernel)
1350 1310 };
1351 1311 };
1352 1312 };
1353 1313
1354 1314 /**
1355 1315 * Prompt the user to restart the IPython kernel.
1356 1316 *
1357 1317 * @method restart_kernel
1358 1318 */
1359 1319 Notebook.prototype.restart_kernel = function () {
1360 1320 var that = this;
1361 1321 var dialog = $('<div/>');
1362 1322 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
1363 1323 $(document).append(dialog);
1364 1324 dialog.dialog({
1365 1325 resizable: false,
1366 1326 modal: true,
1367 1327 title: "Restart kernel or continue running?",
1368 1328 closeText: '',
1369 1329 buttons : {
1370 1330 "Restart": function () {
1371 1331 that.kernel.restart();
1372 1332 $(this).dialog('close');
1373 1333 },
1374 1334 "Continue running": function () {
1375 1335 $(this).dialog('close');
1376 1336 }
1377 1337 }
1378 1338 });
1379 1339 };
1380 1340
1381 1341 /**
1382 1342 * Run the selected cell.
1383 1343 *
1384 1344 * Execute or render cell outputs.
1385 1345 *
1386 1346 * @method execute_selected_cell
1387 1347 * @param {Object} options Customize post-execution behavior
1388 1348 */
1389 1349 Notebook.prototype.execute_selected_cell = function (options) {
1390 1350 // add_new: should a new cell be added if we are at the end of the nb
1391 1351 // terminal: execute in terminal mode, which stays in the current cell
1392 1352 var default_options = {terminal: false, add_new: true};
1393 1353 $.extend(default_options, options);
1394 1354 var that = this;
1395 1355 var cell = that.get_selected_cell();
1396 1356 var cell_index = that.find_cell_index(cell);
1397 1357 if (cell instanceof IPython.CodeCell) {
1398 1358 cell.execute();
1399 } else if (cell instanceof IPython.HTMLCell) {
1400 cell.render();
1401 1359 }
1402 1360 if (default_options.terminal) {
1403 1361 cell.select_all();
1404 1362 } else {
1405 1363 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1406 1364 that.insert_cell_below('code');
1407 1365 // If we are adding a new cell at the end, scroll down to show it.
1408 1366 that.scroll_to_bottom();
1409 1367 } else {
1410 1368 that.select(cell_index+1);
1411 1369 };
1412 1370 };
1413 1371 this.dirty = true;
1414 1372 };
1415 1373
1416 1374 /**
1417 1375 * Execute all cells below the selected cell.
1418 1376 *
1419 1377 * @method execute_cells_below
1420 1378 */
1421 1379 Notebook.prototype.execute_cells_below = function () {
1422 1380 this.execute_cell_range(this.get_selected_index(), this.ncells());
1423 1381 this.scroll_to_bottom();
1424 1382 };
1425 1383
1426 1384 /**
1427 1385 * Execute all cells above the selected cell.
1428 1386 *
1429 1387 * @method execute_cells_above
1430 1388 */
1431 1389 Notebook.prototype.execute_cells_above = function () {
1432 1390 this.execute_cell_range(0, this.get_selected_index());
1433 1391 };
1434 1392
1435 1393 /**
1436 1394 * Execute all cells.
1437 1395 *
1438 1396 * @method execute_all_cells
1439 1397 */
1440 1398 Notebook.prototype.execute_all_cells = function () {
1441 1399 this.execute_cell_range(0, this.ncells());
1442 1400 this.scroll_to_bottom();
1443 1401 };
1444 1402
1445 1403 /**
1446 1404 * Execute a contiguous range of cells.
1447 1405 *
1448 1406 * @method execute_cell_range
1449 1407 * @param {Number} start Index of the first cell to execute (inclusive)
1450 1408 * @param {Number} end Index of the last cell to execute (exclusive)
1451 1409 */
1452 1410 Notebook.prototype.execute_cell_range = function (start, end) {
1453 1411 for (var i=start; i<end; i++) {
1454 1412 this.select(i);
1455 1413 this.execute_selected_cell({add_new:false});
1456 1414 };
1457 1415 };
1458 1416
1459 1417 // Persistance and loading
1460 1418
1461 1419 /**
1462 1420 * Getter method for this notebook's ID.
1463 1421 *
1464 1422 * @method get_notebook_id
1465 1423 * @return {String} This notebook's ID
1466 1424 */
1467 1425 Notebook.prototype.get_notebook_id = function () {
1468 1426 return this.notebook_id;
1469 1427 };
1470 1428
1471 1429 /**
1472 1430 * Getter method for this notebook's name.
1473 1431 *
1474 1432 * @method get_notebook_name
1475 1433 * @return {String} This notebook's name
1476 1434 */
1477 1435 Notebook.prototype.get_notebook_name = function () {
1478 1436 return this.notebook_name;
1479 1437 };
1480 1438
1481 1439 /**
1482 1440 * Setter method for this notebook's name.
1483 1441 *
1484 1442 * @method set_notebook_name
1485 1443 * @param {String} name A new name for this notebook
1486 1444 */
1487 1445 Notebook.prototype.set_notebook_name = function (name) {
1488 1446 this.notebook_name = name;
1489 1447 };
1490 1448
1491 1449 /**
1492 1450 * Check that a notebook's name is valid.
1493 1451 *
1494 1452 * @method test_notebook_name
1495 1453 * @param {String} nbname A name for this notebook
1496 1454 * @return {Boolean} True if the name is valid, false if invalid
1497 1455 */
1498 1456 Notebook.prototype.test_notebook_name = function (nbname) {
1499 1457 nbname = nbname || '';
1500 1458 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1501 1459 return true;
1502 1460 } else {
1503 1461 return false;
1504 1462 };
1505 1463 };
1506 1464
1507 1465 /**
1508 1466 * Load a notebook from JSON (.ipynb).
1509 1467 *
1510 1468 * This currently handles one worksheet: others are deleted.
1511 1469 *
1512 1470 * @method fromJSON
1513 1471 * @param {Object} data JSON representation of a notebook
1514 1472 */
1515 1473 Notebook.prototype.fromJSON = function (data) {
1516 1474 var ncells = this.ncells();
1517 1475 var i;
1518 1476 for (i=0; i<ncells; i++) {
1519 1477 // Always delete cell 0 as they get renumbered as they are deleted.
1520 1478 this.delete_cell(0);
1521 1479 };
1522 1480 // Save the metadata and name.
1523 1481 this.metadata = data.metadata;
1524 1482 this.notebook_name = data.metadata.name;
1525 1483 // Only handle 1 worksheet for now.
1526 1484 var worksheet = data.worksheets[0];
1527 1485 if (worksheet !== undefined) {
1528 1486 if (worksheet.metadata) {
1529 1487 this.worksheet_metadata = worksheet.metadata;
1530 1488 }
1531 1489 var new_cells = worksheet.cells;
1532 1490 ncells = new_cells.length;
1533 1491 var cell_data = null;
1534 1492 var new_cell = null;
1535 1493 for (i=0; i<ncells; i++) {
1536 1494 cell_data = new_cells[i];
1537 1495 // VERSIONHACK: plaintext -> raw
1538 1496 // handle never-released plaintext name for raw cells
1539 1497 if (cell_data.cell_type === 'plaintext'){
1540 1498 cell_data.cell_type = 'raw';
1541 1499 }
1542 1500
1543 1501 new_cell = this.insert_cell_below(cell_data.cell_type);
1544 1502 new_cell.fromJSON(cell_data);
1545 1503 };
1546 1504 };
1547 1505 if (data.worksheets.length > 1) {
1548 1506 var dialog = $('<div/>');
1549 1507 dialog.html("This notebook has " + data.worksheets.length + " worksheets, " +
1550 1508 "but this version of IPython can only handle the first. " +
1551 1509 "If you save this notebook, worksheets after the first will be lost."
1552 1510 );
1553 1511 this.element.append(dialog);
1554 1512 dialog.dialog({
1555 1513 resizable: false,
1556 1514 modal: true,
1557 1515 title: "Multiple worksheets",
1558 1516 closeText: "",
1559 1517 close: function(event, ui) {$(this).dialog('destroy').remove();},
1560 1518 buttons : {
1561 1519 "OK": function () {
1562 1520 $(this).dialog('close');
1563 1521 }
1564 1522 },
1565 1523 width: 400
1566 1524 });
1567 1525 }
1568 1526 };
1569 1527
1570 1528 /**
1571 1529 * Dump this notebook into a JSON-friendly object.
1572 1530 *
1573 1531 * @method toJSON
1574 1532 * @return {Object} A JSON-friendly representation of this notebook.
1575 1533 */
1576 1534 Notebook.prototype.toJSON = function () {
1577 1535 var cells = this.get_cells();
1578 1536 var ncells = cells.length;
1579 1537 var cell_array = new Array(ncells);
1580 1538 for (var i=0; i<ncells; i++) {
1581 1539 cell_array[i] = cells[i].toJSON();
1582 1540 };
1583 1541 var data = {
1584 1542 // Only handle 1 worksheet for now.
1585 1543 worksheets : [{
1586 1544 cells: cell_array,
1587 1545 metadata: this.worksheet_metadata
1588 1546 }],
1589 1547 metadata : this.metadata
1590 1548 };
1591 1549 return data;
1592 1550 };
1593 1551
1594 1552 /**
1595 1553 * Save this notebook on the server.
1596 1554 *
1597 1555 * @method save_notebook
1598 1556 */
1599 1557 Notebook.prototype.save_notebook = function () {
1600 1558 // We may want to move the name/id/nbformat logic inside toJSON?
1601 1559 var data = this.toJSON();
1602 1560 data.metadata.name = this.notebook_name;
1603 1561 data.nbformat = this.nbformat;
1604 1562 data.nbformat_minor = this.nbformat_minor;
1605 1563 // We do the call with settings so we can set cache to false.
1606 1564 var settings = {
1607 1565 processData : false,
1608 1566 cache : false,
1609 1567 type : "PUT",
1610 1568 data : JSON.stringify(data),
1611 1569 headers : {'Content-Type': 'application/json'},
1612 1570 success : $.proxy(this.save_notebook_success,this),
1613 1571 error : $.proxy(this.save_notebook_error,this)
1614 1572 };
1615 1573 $([IPython.events]).trigger('notebook_saving.Notebook');
1616 1574 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1617 1575 $.ajax(url, settings);
1618 1576 };
1619 1577
1620 1578 /**
1621 1579 * Success callback for saving a notebook.
1622 1580 *
1623 1581 * @method save_notebook_success
1624 1582 * @param {Object} data JSON representation of a notebook
1625 1583 * @param {String} status Description of response status
1626 1584 * @param {jqXHR} xhr jQuery Ajax object
1627 1585 */
1628 1586 Notebook.prototype.save_notebook_success = function (data, status, xhr) {
1629 1587 this.dirty = false;
1630 1588 $([IPython.events]).trigger('notebook_saved.Notebook');
1631 1589 };
1632 1590
1633 1591 /**
1634 1592 * Failure callback for saving a notebook.
1635 1593 *
1636 1594 * @method save_notebook_error
1637 1595 * @param {jqXHR} xhr jQuery Ajax object
1638 1596 * @param {String} status Description of response status
1639 1597 * @param {String} error_msg HTTP error message
1640 1598 */
1641 1599 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1642 1600 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1643 1601 };
1644 1602
1645 1603 /**
1646 1604 * Request a notebook's data from the server.
1647 1605 *
1648 1606 * @method load_notebook
1649 1607 * @param {String} notebook_id A notebook to load
1650 1608 */
1651 1609 Notebook.prototype.load_notebook = function (notebook_id) {
1652 1610 var that = this;
1653 1611 this.notebook_id = notebook_id;
1654 1612 // We do the call with settings so we can set cache to false.
1655 1613 var settings = {
1656 1614 processData : false,
1657 1615 cache : false,
1658 1616 type : "GET",
1659 1617 dataType : "json",
1660 1618 success : $.proxy(this.load_notebook_success,this),
1661 1619 error : $.proxy(this.load_notebook_error,this),
1662 1620 };
1663 1621 $([IPython.events]).trigger('notebook_loading.Notebook');
1664 1622 var url = this.baseProjectUrl() + 'notebooks/' + this.notebook_id;
1665 1623 $.ajax(url, settings);
1666 1624 };
1667 1625
1668 1626 /**
1669 1627 * Success callback for loading a notebook from the server.
1670 1628 *
1671 1629 * Load notebook data from the JSON response.
1672 1630 *
1673 1631 * @method load_notebook_success
1674 1632 * @param {Object} data JSON representation of a notebook
1675 1633 * @param {String} status Description of response status
1676 1634 * @param {jqXHR} xhr jQuery Ajax object
1677 1635 */
1678 1636 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1679 1637 this.fromJSON(data);
1680 1638 if (this.ncells() === 0) {
1681 1639 this.insert_cell_below('code');
1682 1640 };
1683 1641 this.dirty = false;
1684 1642 this.select(0);
1685 1643 this.scroll_to_top();
1686 1644 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1687 1645 msg = "This notebook has been converted from an older " +
1688 1646 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1689 1647 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1690 1648 "newer notebook format will be used and older verions of IPython " +
1691 1649 "may not be able to read it. To keep the older version, close the " +
1692 1650 "notebook without saving it.";
1693 1651 var dialog = $('<div/>');
1694 1652 dialog.html(msg);
1695 1653 this.element.append(dialog);
1696 1654 dialog.dialog({
1697 1655 resizable: false,
1698 1656 modal: true,
1699 1657 title: "Notebook converted",
1700 1658 closeText: "",
1701 1659 close: function(event, ui) {$(this).dialog('destroy').remove();},
1702 1660 buttons : {
1703 1661 "OK": function () {
1704 1662 $(this).dialog('close');
1705 1663 }
1706 1664 },
1707 1665 width: 400
1708 1666 });
1709 1667 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1710 1668 var that = this;
1711 1669 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1712 1670 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1713 1671 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1714 1672 this_vs + ". You can still work with this notebook, but some features " +
1715 1673 "introduced in later notebook versions may not be available."
1716 1674
1717 1675 var dialog = $('<div/>');
1718 1676 dialog.html(msg);
1719 1677 this.element.append(dialog);
1720 1678 dialog.dialog({
1721 1679 resizable: false,
1722 1680 modal: true,
1723 1681 title: "Newer Notebook",
1724 1682 closeText: "",
1725 1683 close: function(event, ui) {$(this).dialog('destroy').remove();},
1726 1684 buttons : {
1727 1685 "OK": function () {
1728 1686 $(this).dialog('close');
1729 1687 }
1730 1688 },
1731 1689 width: 400
1732 1690 });
1733 1691
1734 1692 }
1735 1693 // Create the kernel after the notebook is completely loaded to prevent
1736 1694 // code execution upon loading, which is a security risk.
1737 1695 if (! this.read_only) {
1738 1696 this.start_kernel();
1739 1697 }
1740 1698 $([IPython.events]).trigger('notebook_loaded.Notebook');
1741 1699 };
1742 1700
1743 1701 /**
1744 1702 * Failure callback for loading a notebook from the server.
1745 1703 *
1746 1704 * @method load_notebook_error
1747 1705 * @param {jqXHR} xhr jQuery Ajax object
1748 1706 * @param {String} textStatus Description of response status
1749 1707 * @param {String} errorThrow HTTP error message
1750 1708 */
1751 1709 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1752 1710 if (xhr.status === 500) {
1753 1711 var msg = "An error occurred while loading this notebook. Most likely " +
1754 1712 "this notebook is in a newer format than is supported by this " +
1755 1713 "version of IPython. This version can load notebook formats " +
1756 1714 "v"+this.nbformat+" or earlier.";
1757 1715 var dialog = $('<div/>');
1758 1716 dialog.html(msg);
1759 1717 this.element.append(dialog);
1760 1718 dialog.dialog({
1761 1719 resizable: false,
1762 1720 modal: true,
1763 1721 title: "Error loading notebook",
1764 1722 closeText: "",
1765 1723 close: function(event, ui) {$(this).dialog('destroy').remove();},
1766 1724 buttons : {
1767 1725 "OK": function () {
1768 1726 $(this).dialog('close');
1769 1727 }
1770 1728 },
1771 1729 width: 400
1772 1730 });
1773 1731 }
1774 1732 }
1775 1733
1776 1734 IPython.Notebook = Notebook;
1777 1735
1778 1736
1779 1737 return IPython;
1780 1738
1781 1739 }(IPython));
1782 1740
@@ -1,557 +1,557
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 // OutputArea
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14
15 15 var utils = IPython.utils;
16 16
17 17 var OutputArea = function (selector, prompt_area) {
18 18 this.selector = selector;
19 19 this.wrapper = $(selector);
20 20 this.outputs = [];
21 21 this.collapsed = false;
22 22 this.scrolled = false;
23 23 this.clear_out_timeout = null;
24 24 if (prompt_area === undefined) {
25 25 this.prompt_area = true;
26 26 } else {
27 27 this.prompt_area = prompt_area;
28 28 }
29 29 this.create_elements();
30 30 this.style();
31 31 this.bind_events();
32 32 };
33 33
34 34 OutputArea.prototype.create_elements = function () {
35 35 this.element = $("<div/>");
36 36 this.collapse_button = $("<div/>");
37 37 this.prompt_overlay = $("<div/>");
38 38 this.wrapper.append(this.prompt_overlay);
39 39 this.wrapper.append(this.element);
40 40 this.wrapper.append(this.collapse_button);
41 41 };
42 42
43 43
44 44 OutputArea.prototype.style = function () {
45 45 this.collapse_button.hide();
46 46 this.prompt_overlay.hide();
47 47
48 48 this.wrapper.addClass('output_wrapper');
49 49 this.element.addClass('output vbox');
50 50
51 51 this.collapse_button.button();
52 52 this.collapse_button.addClass('output_collapsed vbox');
53 53 this.collapse_button.attr('title', 'click to expand output');
54 54 this.collapse_button.html('. . .');
55 55
56 56 this.prompt_overlay.addClass('out_prompt_overlay prompt');
57 57 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
58 58
59 59 this.collapse();
60 60 };
61 61
62 62
63 63 OutputArea.prototype._should_scroll = function (lines) {
64 64 if (!lines) {
65 65 lines = 100;
66 66 }
67 67 // line-height from http://stackoverflow.com/questions/1185151
68 68 var fontSize = this.element.css('font-size');
69 69 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
70 70
71 71 return (this.element.height() > lines * lineHeight);
72 72 };
73 73
74 74
75 75 OutputArea.prototype.bind_events = function () {
76 76 var that = this;
77 77 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
78 78 this.prompt_overlay.click(function () { that.toggle_scroll(); });
79 79
80 80 this.element.resize(function () {
81 81 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
82 82 if ( IPython.utils.browser[0] === "Firefox" ) {
83 83 return;
84 84 }
85 85 // maybe scroll output,
86 86 // if it's grown large enough and hasn't already been scrolled.
87 87 if ( !that.scrolled && that._should_scroll()) {
88 88 that.scroll_area();
89 89 }
90 90 });
91 91 this.collapse_button.click(function () {
92 92 that.expand();
93 93 });
94 94 this.collapse_button.hover(function () {
95 95 $(this).addClass("ui-state-hover");
96 96 }, function () {
97 97 $(this).removeClass("ui-state-hover");
98 98 });
99 99 };
100 100
101 101
102 102 OutputArea.prototype.collapse = function () {
103 103 if (!this.collapsed) {
104 104 this.element.hide();
105 105 this.prompt_overlay.hide();
106 106 if (this.element.html()){
107 107 this.collapse_button.show();
108 108 }
109 109 this.collapsed = true;
110 110 }
111 111 };
112 112
113 113
114 114 OutputArea.prototype.expand = function () {
115 115 if (this.collapsed) {
116 116 this.collapse_button.hide();
117 117 this.element.show();
118 118 this.prompt_overlay.show();
119 119 this.collapsed = false;
120 120 }
121 121 };
122 122
123 123
124 124 OutputArea.prototype.toggle_output = function () {
125 125 if (this.collapsed) {
126 126 this.expand();
127 127 } else {
128 128 this.collapse();
129 129 }
130 130 };
131 131
132 132
133 133 OutputArea.prototype.scroll_area = function () {
134 134 this.element.addClass('output_scroll');
135 135 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
136 136 this.scrolled = true;
137 137 };
138 138
139 139
140 140 OutputArea.prototype.unscroll_area = function () {
141 141 this.element.removeClass('output_scroll');
142 142 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
143 143 this.scrolled = false;
144 144 };
145 145
146 146
147 147 OutputArea.prototype.scroll_if_long = function (lines) {
148 148 if (this._should_scroll(lines)) {
149 149 // only allow scrolling long-enough output
150 150 this.scroll_area();
151 151 }
152 152 };
153 153
154 154
155 155 OutputArea.prototype.toggle_scroll = function () {
156 156 if (this.scrolled) {
157 157 this.unscroll_area();
158 158 } else {
159 159 // only allow scrolling long-enough output
160 160 this.scroll_if_long(20);
161 161 }
162 162 };
163 163
164 164
165 165 // typeset with MathJax if MathJax is available
166 166 OutputArea.prototype.typeset = function () {
167 167 if (window.MathJax){
168 168 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
169 169 }
170 170 };
171 171
172 172
173 173 OutputArea.prototype.handle_output = function (msg_type, content) {
174 174 var json = {};
175 175 json.output_type = msg_type;
176 176 if (msg_type === "stream") {
177 177 json.text = content.data;
178 178 json.stream = content.name;
179 179 } else if (msg_type === "display_data") {
180 180 json = this.convert_mime_types(json, content.data);
181 181 } else if (msg_type === "pyout") {
182 182 json.prompt_number = content.execution_count;
183 183 json = this.convert_mime_types(json, content.data);
184 184 } else if (msg_type === "pyerr") {
185 185 json.ename = content.ename;
186 186 json.evalue = content.evalue;
187 187 json.traceback = content.traceback;
188 188 }
189 189 // append with dynamic=true
190 190 this.append_output(json, true);
191 191 };
192 192
193 193
194 194 OutputArea.prototype.convert_mime_types = function (json, data) {
195 195 if (data['text/plain'] !== undefined) {
196 196 json.text = data['text/plain'];
197 197 }
198 198 if (data['text/html'] !== undefined) {
199 199 json.html = data['text/html'];
200 200 }
201 201 if (data['image/svg+xml'] !== undefined) {
202 202 json.svg = data['image/svg+xml'];
203 203 }
204 204 if (data['image/png'] !== undefined) {
205 205 json.png = data['image/png'];
206 206 }
207 207 if (data['image/jpeg'] !== undefined) {
208 208 json.jpeg = data['image/jpeg'];
209 209 }
210 210 if (data['text/latex'] !== undefined) {
211 211 json.latex = data['text/latex'];
212 212 }
213 213 if (data['application/json'] !== undefined) {
214 214 json.json = data['application/json'];
215 215 }
216 216 if (data['application/javascript'] !== undefined) {
217 217 json.javascript = data['application/javascript'];
218 218 }
219 219 return json;
220 220 };
221 221
222 222
223 223 OutputArea.prototype.append_output = function (json, dynamic) {
224 224 // If dynamic is true, javascript output will be eval'd.
225 225 this.expand();
226 226 this.flush_clear_timeout();
227 227 if (json.output_type === 'pyout') {
228 228 this.append_pyout(json, dynamic);
229 229 } else if (json.output_type === 'pyerr') {
230 230 this.append_pyerr(json);
231 231 } else if (json.output_type === 'display_data') {
232 232 this.append_display_data(json, dynamic);
233 233 } else if (json.output_type === 'stream') {
234 234 this.append_stream(json);
235 235 }
236 236 this.outputs.push(json);
237 237 var that = this;
238 238 setTimeout(function(){that.element.trigger('resize');}, 100);
239 239 };
240 240
241 241
242 242 OutputArea.prototype.create_output_area = function () {
243 243 var oa = $("<div/>").addClass("output_area");
244 244 if (this.prompt_area) {
245 245 oa.append($('<div/>').addClass('prompt'));
246 246 }
247 247 return oa;
248 248 };
249 249
250 250
251 251 OutputArea.prototype.append_pyout = function (json, dynamic) {
252 252 var n = json.prompt_number || ' ';
253 253 var toinsert = this.create_output_area();
254 254 if (this.prompt_area) {
255 255 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
256 256 }
257 257 this.append_mime_type(json, toinsert, dynamic);
258 258 this.element.append(toinsert);
259 259 // If we just output latex, typeset it.
260 260 if ((json.latex !== undefined) || (json.html !== undefined)) {
261 261 this.typeset();
262 262 }
263 263 };
264 264
265 265
266 266 OutputArea.prototype.append_pyerr = function (json) {
267 267 var tb = json.traceback;
268 268 if (tb !== undefined && tb.length > 0) {
269 269 var s = '';
270 270 var len = tb.length;
271 271 for (var i=0; i<len; i++) {
272 272 s = s + tb[i] + '\n';
273 273 }
274 274 s = s + '\n';
275 275 var toinsert = this.create_output_area();
276 276 this.append_text(s, toinsert);
277 277 this.element.append(toinsert);
278 278 }
279 279 };
280 280
281 281
282 282 OutputArea.prototype.append_stream = function (json) {
283 283 // temporary fix: if stream undefined (json file written prior to this patch),
284 284 // default to most likely stdout:
285 285 if (json.stream == undefined){
286 286 json.stream = 'stdout';
287 287 }
288 288 var text = json.text;
289 289 var subclass = "output_"+json.stream;
290 290 if (this.outputs.length > 0){
291 291 // have at least one output to consider
292 292 var last = this.outputs[this.outputs.length-1];
293 293 if (last.output_type == 'stream' && json.stream == last.stream){
294 294 // latest output was in the same stream,
295 295 // so append directly into its pre tag
296 296 // escape ANSI & HTML specials:
297 297 var pre = this.element.find('div.'+subclass).last().find('pre');
298 298 var html = utils.fixCarriageReturn(
299 299 pre.html() + utils.fixConsole(text));
300 300 pre.html(html);
301 301 return;
302 302 }
303 303 }
304 304
305 305 if (!text.replace("\r", "")) {
306 306 // text is nothing (empty string, \r, etc.)
307 307 // so don't append any elements, which might add undesirable space
308 308 return;
309 309 }
310 310
311 311 // If we got here, attach a new div
312 312 var toinsert = this.create_output_area();
313 313 this.append_text(text, toinsert, "output_stream "+subclass);
314 314 this.element.append(toinsert);
315 315 };
316 316
317 317
318 318 OutputArea.prototype.append_display_data = function (json, dynamic) {
319 319 var toinsert = this.create_output_area();
320 320 this.append_mime_type(json, toinsert, dynamic);
321 321 this.element.append(toinsert);
322 322 // If we just output latex, typeset it.
323 323 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
324 324 this.typeset();
325 325 }
326 326 };
327 327
328 OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
328 OutputArea.display_order = ['javascript','latex','svg','png','jpeg','text'];
329 329
330 330 OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
331 331 for(var type_i in OutputArea.display_order){
332 332 var type = OutputArea.display_order[type_i];
333 333 if(json[type] != undefined ){
334 334 if(type == 'javascript'){
335 335 if (dynamic) {
336 336 this.append_javascript(json.javascript, element, dynamic);
337 337 }
338 338 } else {
339 339 this['append_'+type](json[type], element);
340 340 }
341 341 return;
342 342 }
343 343 }
344 344 };
345 345
346 346
347 347 OutputArea.prototype.append_html = function (html, element) {
348 348 var toinsert = $("<div/>").addClass("output_subarea output_html rendered_html");
349 349 toinsert.append(html);
350 350 element.append(toinsert);
351 351 };
352 352
353 353
354 354 OutputArea.prototype.append_javascript = function (js, container) {
355 355 // We just eval the JS code, element appears in the local scope.
356 356 var element = $("<div/>").addClass("output_subarea");
357 357 container.append(element);
358 358 // Div for js shouldn't be drawn, as it will add empty height to the area.
359 359 container.hide();
360 360 // If the Javascript appends content to `element` that should be drawn, then
361 361 // it must also call `container.show()`.
362 362 try {
363 363 eval(js);
364 364 } catch(err) {
365 365 console.log('Error in Javascript!');
366 366 console.log(err);
367 367 container.show();
368 368 element.append($('<div/>')
369 369 .html("Error in Javascript !<br/>"+
370 370 err.toString()+
371 371 '<br/>See your browser Javascript console for more details.')
372 372 .addClass('js-error')
373 373 );
374 374 }
375 375 };
376 376
377 377
378 378 OutputArea.prototype.append_text = function (data, element, extra_class) {
379 379 var toinsert = $("<div/>").addClass("output_subarea output_text");
380 380 // escape ANSI & HTML specials in plaintext:
381 381 data = utils.fixConsole(data);
382 382 data = utils.fixCarriageReturn(data);
383 383 data = utils.autoLinkUrls(data);
384 384 if (extra_class){
385 385 toinsert.addClass(extra_class);
386 386 }
387 387 toinsert.append($("<pre/>").html(data));
388 388 element.append(toinsert);
389 389 };
390 390
391 391
392 392 OutputArea.prototype.append_svg = function (svg, element) {
393 393 var toinsert = $("<div/>").addClass("output_subarea output_svg");
394 394 toinsert.append(svg);
395 395 element.append(toinsert);
396 396 };
397 397
398 398
399 399 OutputArea.prototype._dblclick_to_reset_size = function (img) {
400 400 // schedule wrapping image in resizable after a delay,
401 401 // so we don't end up calling resize on a zero-size object
402 402 var that = this;
403 403 setTimeout(function () {
404 404 var h0 = img.height();
405 405 var w0 = img.width();
406 406 if (!(h0 && w0)) {
407 407 // zero size, schedule another timeout
408 408 that._dblclick_to_reset_size(img);
409 409 return;
410 410 }
411 411 img.resizable({
412 412 aspectRatio: true,
413 413 autoHide: true
414 414 });
415 415 img.dblclick(function () {
416 416 // resize wrapper & image together for some reason:
417 417 img.parent().height(h0);
418 418 img.height(h0);
419 419 img.parent().width(w0);
420 420 img.width(w0);
421 421 });
422 422 }, 250);
423 423 };
424 424
425 425
426 426 OutputArea.prototype.append_png = function (png, element) {
427 427 var toinsert = $("<div/>").addClass("output_subarea output_png");
428 428 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
429 429 this._dblclick_to_reset_size(img);
430 430 toinsert.append(img);
431 431 element.append(toinsert);
432 432 };
433 433
434 434
435 435 OutputArea.prototype.append_jpeg = function (jpeg, element) {
436 436 var toinsert = $("<div/>").addClass("output_subarea output_jpeg");
437 437 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
438 438 this._dblclick_to_reset_size(img);
439 439 toinsert.append(img);
440 440 element.append(toinsert);
441 441 };
442 442
443 443
444 444 OutputArea.prototype.append_latex = function (latex, element) {
445 445 // This method cannot do the typesetting because the latex first has to
446 446 // be on the page.
447 447 var toinsert = $("<div/>").addClass("output_subarea output_latex");
448 448 toinsert.append(latex);
449 449 element.append(toinsert);
450 450 };
451 451
452 452
453 453 OutputArea.prototype.handle_clear_output = function (content) {
454 454 this.clear_output(content.stdout, content.stderr, content.other);
455 455 };
456 456
457 457
458 458 OutputArea.prototype.clear_output = function (stdout, stderr, other) {
459 459 var that = this;
460 460 if (this.clear_out_timeout != null){
461 461 // fire previous pending clear *immediately*
462 462 clearTimeout(this.clear_out_timeout);
463 463 this.clear_out_timeout = null;
464 464 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
465 465 }
466 466 // store flags for flushing the timeout
467 467 this._clear_stdout = stdout;
468 468 this._clear_stderr = stderr;
469 469 this._clear_other = other;
470 470 this.clear_out_timeout = setTimeout(function() {
471 471 // really clear timeout only after a short delay
472 472 // this reduces flicker in 'clear_output; print' cases
473 473 that.clear_out_timeout = null;
474 474 that._clear_stdout = that._clear_stderr = that._clear_other = null;
475 475 that.clear_output_callback(stdout, stderr, other);
476 476 }, 500
477 477 );
478 478 };
479 479
480 480
481 481 OutputArea.prototype.clear_output_callback = function (stdout, stderr, other) {
482 482 var output_div = this.element;
483 483
484 484 if (stdout && stderr && other){
485 485 // clear all, no need for logic
486 486 output_div.html("");
487 487 this.outputs = [];
488 488 this.unscroll_area();
489 489 return;
490 490 }
491 491 // remove html output
492 492 // each output_subarea that has an identifying class is in an output_area
493 493 // which is the element to be removed.
494 494 if (stdout) {
495 495 output_div.find("div.output_stdout").parent().remove();
496 496 }
497 497 if (stderr) {
498 498 output_div.find("div.output_stderr").parent().remove();
499 499 }
500 500 if (other) {
501 501 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
502 502 }
503 503 this.unscroll_area();
504 504
505 505 // remove cleared outputs from JSON list:
506 506 for (var i = this.outputs.length - 1; i >= 0; i--) {
507 507 var out = this.outputs[i];
508 508 var output_type = out.output_type;
509 509 if (output_type == "display_data" && other) {
510 510 this.outputs.splice(i,1);
511 511 } else if (output_type == "stream") {
512 512 if (stdout && out.stream == "stdout") {
513 513 this.outputs.splice(i,1);
514 514 } else if (stderr && out.stream == "stderr") {
515 515 this.outputs.splice(i,1);
516 516 }
517 517 }
518 518 }
519 519 };
520 520
521 521
522 522 OutputArea.prototype.flush_clear_timeout = function() {
523 523 var output_div = this.element;
524 524 if (this.clear_out_timeout){
525 525 clearTimeout(this.clear_out_timeout);
526 526 this.clear_out_timeout = null;
527 527 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
528 528 }
529 529 };
530 530
531 531
532 532 // JSON serialization
533 533
534 534 OutputArea.prototype.fromJSON = function (outputs) {
535 535 var len = outputs.length;
536 536 for (var i=0; i<len; i++) {
537 537 // append with dynamic=false.
538 538 this.append_output(outputs[i], false);
539 539 }
540 540 };
541 541
542 542
543 543 OutputArea.prototype.toJSON = function () {
544 544 var outputs = [];
545 545 var len = this.outputs.length;
546 546 for (var i=0; i<len; i++) {
547 547 outputs[i] = this.outputs[i];
548 548 }
549 549 return outputs;
550 550 };
551 551
552 552
553 553 IPython.OutputArea = OutputArea;
554 554
555 555 return IPython;
556 556
557 557 }(IPython));
@@ -1,598 +1,558
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2012 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 // TextCell
10 10 //============================================================================
11 11
12 12
13 13
14 14 /**
15 15 A module that allow to create different type of Text Cell
16 16 @module IPython
17 17 @namespace IPython
18 18 */
19 19 var IPython = (function (IPython) {
20 20
21 21 // TextCell base class
22 22 var key = IPython.utils.keycodes;
23 23
24 24 /**
25 25 * Construct a new TextCell, codemirror mode is by default 'htmlmixed', and cell type is 'text'
26 26 * cell start as not redered.
27 27 *
28 28 * @class TextCell
29 29 * @constructor TextCell
30 30 * @extend Ipython.Cell
31 31 * @param {object|undefined} [options]
32 32 * @param [options.cm_config] {object} config to pass to CodeMirror, will extend/overwrite default config
33 33 * @param [options.placeholder] {string} default string to use when souce in empty for rendering (only use in some TextCell subclass)
34 34 */
35 35 var TextCell = function (options) {
36 36 // in all TextCell/Cell subclasses
37 37 // do not assign most of members here, just pass it down
38 38 // in the options dict potentially overwriting what you wish.
39 39 // they will be assigned in the base class.
40 40
41 41 // we cannot put this as a class key as it has handle to "this".
42 42 var cm_overwrite_options = {
43 43 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
44 44 };
45 45
46 46 options = this.mergeopt(TextCell,options,{cm_config:cm_overwrite_options});
47 47
48 48 IPython.Cell.apply(this, [options]);
49 49
50 50
51 51 this.rendered = false;
52 52 this.cell_type = this.cell_type || 'text';
53 53 };
54 54
55 55 TextCell.prototype = new IPython.Cell();
56 56
57 57 TextCell.options_default = {
58 58 cm_config : {
59 59 extraKeys: {"Tab": "indentMore","Shift-Tab" : "indentLess"},
60 60 mode: 'htmlmixed',
61 61 lineWrapping : true,
62 62 }
63 63 };
64 64
65 65
66 66
67 67 /**
68 68 * Create the DOM element of the TextCell
69 69 * @method create_element
70 70 * @private
71 71 */
72 72 TextCell.prototype.create_element = function () {
73 73 IPython.Cell.prototype.create_element.apply(this, arguments);
74 74 var cell = $("<div>").addClass('cell text_cell border-box-sizing');
75 75 cell.attr('tabindex','2');
76 76
77 77 this.celltoolbar = new IPython.CellToolbar(this);
78 78 cell.append(this.celltoolbar.element);
79 79
80 80 var input_area = $('<div/>').addClass('text_cell_input border-box-sizing');
81 81 this.code_mirror = CodeMirror(input_area.get(0), this.cm_config);
82 82
83 83 // The tabindex=-1 makes this div focusable.
84 84 var render_area = $('<div/>').addClass('text_cell_render border-box-sizing').
85 85 addClass('rendered_html').attr('tabindex','-1');
86 86 cell.append(input_area).append(render_area);
87 87 this.element = cell;
88 88 };
89 89
90 90
91 91 /**
92 92 * Bind the DOM evet to cell actions
93 93 * Need to be called after TextCell.create_element
94 94 * @private
95 95 * @method bind_event
96 96 */
97 97 TextCell.prototype.bind_events = function () {
98 98 IPython.Cell.prototype.bind_events.apply(this);
99 99 var that = this;
100 100 this.element.keydown(function (event) {
101 101 if (event.which === 13 && !event.shiftKey) {
102 102 if (that.rendered) {
103 103 that.edit();
104 104 return false;
105 105 };
106 106 };
107 107 });
108 108 this.element.dblclick(function () {
109 109 that.edit();
110 110 });
111 111 };
112 112
113 113 /**
114 114 * This method gets called in CodeMirror's onKeyDown/onKeyPress
115 115 * handlers and is used to provide custom key handling.
116 116 *
117 117 * Subclass should override this method to have custom handeling
118 118 *
119 119 * @method handle_codemirror_keyevent
120 120 * @param {CodeMirror} editor - The codemirror instance bound to the cell
121 121 * @param {event} event -
122 122 * @return {Boolean} `true` if CodeMirror should ignore the event, `false` Otherwise
123 123 */
124 124 TextCell.prototype.handle_codemirror_keyevent = function (editor, event) {
125 125
126 126 if (event.keyCode === 13 && (event.shiftKey || event.ctrlKey)) {
127 127 // Always ignore shift-enter in CodeMirror as we handle it.
128 128 return true;
129 129 }
130 130 return false;
131 131 };
132 132
133 133 /**
134 134 * Select the current cell and trigger 'focus'
135 135 * @method select
136 136 */
137 137 TextCell.prototype.select = function () {
138 138 IPython.Cell.prototype.select.apply(this);
139 139 var output = this.element.find("div.text_cell_render");
140 140 output.trigger('focus');
141 141 };
142 142
143 143 /**
144 144 * unselect the current cell and `render` it
145 145 * @method unselect
146 146 */
147 147 TextCell.prototype.unselect = function() {
148 148 // render on selection of another cell
149 149 this.render();
150 150 IPython.Cell.prototype.unselect.apply(this);
151 151 };
152 152
153 153 /**
154 154 *
155 155 * put the current cell in edition mode
156 156 * @method edit
157 157 */
158 158 TextCell.prototype.edit = function () {
159 159 if ( this.read_only ) return;
160 160 if (this.rendered === true) {
161 161 var text_cell = this.element;
162 162 var output = text_cell.find("div.text_cell_render");
163 163 output.hide();
164 164 text_cell.find('div.text_cell_input').show();
165 165 this.code_mirror.refresh();
166 166 this.code_mirror.focus();
167 167 // We used to need an additional refresh() after the focus, but
168 168 // it appears that this has been fixed in CM. This bug would show
169 169 // up on FF when a newly loaded markdown cell was edited.
170 170 this.rendered = false;
171 171 if (this.get_text() === this.placeholder) {
172 172 this.set_text('');
173 173 this.refresh();
174 174 }
175 175 }
176 176 };
177 177
178 178
179 179 /**
180 180 * Empty, Subclasses must define render.
181 181 * @method render
182 182 */
183 183 TextCell.prototype.render = function () {};
184 184
185 185
186 186 /**
187 187 * setter: {{#crossLink "TextCell/set_text"}}{{/crossLink}}
188 188 * @method get_text
189 189 * @retrun {string} CodeMirror current text value
190 190 */
191 191 TextCell.prototype.get_text = function() {
192 192 return this.code_mirror.getValue();
193 193 };
194 194
195 195 /**
196 196 * @param {string} text - Codemiror text value
197 197 * @see TextCell#get_text
198 198 * @method set_text
199 199 * */
200 200 TextCell.prototype.set_text = function(text) {
201 201 this.code_mirror.setValue(text);
202 202 this.code_mirror.refresh();
203 203 };
204 204
205 205 /**
206 206 * setter :{{#crossLink "TextCell/set_rendered"}}{{/crossLink}}
207 207 * @method get_rendered
208 208 * @return {html} html of rendered element
209 209 * */
210 210 TextCell.prototype.get_rendered = function() {
211 211 return this.element.find('div.text_cell_render').html();
212 212 };
213 213
214 214 /**
215 215 * @method set_rendered
216 216 */
217 217 TextCell.prototype.set_rendered = function(text) {
218 218 this.element.find('div.text_cell_render').html(text);
219 219 };
220 220
221 221 /**
222 222 * not deprecated, but implementation wrong
223 223 * @method at_top
224 224 * @deprecated
225 225 * @return {Boolean} true is cell rendered, false otherwise
226 226 * I doubt this is what it is supposed to do
227 227 * this implementation is completly false
228 228 */
229 229 TextCell.prototype.at_top = function () {
230 230 if (this.rendered) {
231 231 return true;
232 232 } else {
233 233 return false;
234 234 }
235 235 };
236 236
237 237
238 238 /**
239 239 * not deprecated, but implementation wrong
240 240 * @method at_bottom
241 241 * @deprecated
242 242 * @return {Boolean} true is cell rendered, false otherwise
243 243 * I doubt this is what it is supposed to do
244 244 * this implementation is completly false
245 245 * */
246 246 TextCell.prototype.at_bottom = function () {
247 247 if (this.rendered) {
248 248 return true;
249 249 } else {
250 250 return false;
251 251 }
252 252 };
253 253
254 254 /**
255 255 * Create Text cell from JSON
256 256 * @param {json} data - JSON serialized text-cell
257 257 * @method fromJSON
258 258 */
259 259 TextCell.prototype.fromJSON = function (data) {
260 260 IPython.Cell.prototype.fromJSON.apply(this, arguments);
261 261 if (data.cell_type === this.cell_type) {
262 262 if (data.source !== undefined) {
263 263 this.set_text(data.source);
264 264 // make this value the starting point, so that we can only undo
265 265 // to this state, instead of a blank cell
266 266 this.code_mirror.clearHistory();
267 267 this.set_rendered(data.rendered || '');
268 268 this.rendered = false;
269 269 this.render();
270 270 }
271 271 }
272 272 };
273 273
274 274 /** Generate JSON from cell
275 275 * @return {object} cell data serialised to json
276 276 */
277 277 TextCell.prototype.toJSON = function () {
278 278 var data = IPython.Cell.prototype.toJSON.apply(this);
279 279 data.cell_type = this.cell_type;
280 280 data.source = this.get_text();
281 281 return data;
282 282 };
283 283
284 284
285 285 /**
286 * @constructor HtmlCell
287 * @class HtmlCell
288 * @extends Ipython.TextCell
289 */
290 var HTMLCell = function (options) {
291
292 options = this.mergeopt(HTMLCell,options);
293 TextCell.apply(this, [options]);
294
295 this.cell_type = 'html';
296 };
297
298 HTMLCell.options_default = {
299 cm_config : {
300 mode: 'htmlmixed',
301 },
302 placeholder: "Type <strong>HTML</strong> and LaTeX: $\\alpha^2$"
303 };
304
305
306 HTMLCell.prototype = new TextCell();
307
308 /**
309 * @method render
310 */
311 HTMLCell.prototype.render = function () {
312 if (this.rendered === false) {
313 var text = this.get_text();
314 if (text === "") { text = this.placeholder; }
315 this.set_rendered(text);
316 this.typeset();
317 this.element.find('div.text_cell_input').hide();
318 this.element.find("div.text_cell_render").show();
319 this.rendered = true;
320 }
321 };
322
323
324 /**
325 286 * @class MarkdownCell
326 287 * @constructor MarkdownCell
327 288 * @extends Ipython.HtmlCell
328 289 */
329 290 var MarkdownCell = function (options) {
330 291 var options = options || {};
331 292
332 293 options = this.mergeopt(MarkdownCell,options);
333 294 TextCell.apply(this, [options]);
334 295
335 296 this.cell_type = 'markdown';
336 297 };
337 298
338 299 MarkdownCell.options_default = {
339 300 cm_config: {
340 301 mode: 'markdown'
341 302 },
342 303 placeholder: "Type *Markdown* and LaTeX: $\\alpha^2$"
343 304 }
344 305
345 306
346 307
347 308
348 309 MarkdownCell.prototype = new TextCell();
349 310
350 311 /**
351 312 * @method render
352 313 */
353 314 MarkdownCell.prototype.render = function () {
354 315 if (this.rendered === false) {
355 316 var text = this.get_text();
356 317 if (text === "") { text = this.placeholder; }
357 318 text = IPython.mathjaxutils.remove_math(text)
358 319 var html = IPython.markdown_converter.makeHtml(text);
359 320 html = IPython.mathjaxutils.replace_math(html)
360 321 try {
361 322 this.set_rendered(html);
362 323 } catch (e) {
363 324 console.log("Error running Javascript in Markdown:");
364 325 console.log(e);
365 326 this.set_rendered($("<div/>").addClass("js-error").html(
366 327 "Error rendering Markdown!<br/>" + e.toString())
367 328 );
368 329 }
369 330 this.element.find('div.text_cell_input').hide();
370 331 this.element.find("div.text_cell_render").show();
371 332 var code_snippets = this.element.find("pre > code");
372 333 code_snippets.replaceWith(function () {
373 334 var code = $(this).html();
374 335 /* Substitute br for newlines and &nbsp; for spaces
375 336 before highlighting, since prettify doesn't
376 337 preserve those on all browsers */
377 338 code = code.replace(/(\r\n|\n|\r)/gm, "<br/>");
378 339 code = code.replace(/ /gm, '&nbsp;');
379 340 code = prettyPrintOne(code);
380 341
381 342 return '<code class="prettyprint">' + code + '</code>';
382 343 });
383 344 this.typeset()
384 345 this.rendered = true;
385 346 }
386 347 };
387 348
388 349
389 350 // RawCell
390 351
391 352 /**
392 353 * @class RawCell
393 354 * @constructor RawCell
394 355 * @extends Ipython.TextCell
395 356 */
396 357 var RawCell = function (options) {
397 358
398 359 options = this.mergeopt(RawCell,options)
399 360 TextCell.apply(this, [options]);
400 361
401 362 this.cell_type = 'raw';
402 363
403 364 var that = this
404 365 this.element.focusout(
405 366 function() { that.auto_highlight(); }
406 367 );
407 368 };
408 369
409 370 RawCell.options_default = {
410 371 placeholder : "Type plain text and LaTeX: $\\alpha^2$"
411 372 };
412 373
413 374
414 375
415 376 RawCell.prototype = new TextCell();
416 377
417 378 /**
418 379 * Trigger autodetection of highlight scheme for current cell
419 380 * @method auto_highlight
420 381 */
421 382 RawCell.prototype.auto_highlight = function () {
422 383 this._auto_highlight(IPython.config.raw_cell_highlight);
423 384 };
424 385
425 386 /** @method render **/
426 387 RawCell.prototype.render = function () {
427 388 this.rendered = true;
428 389 this.edit();
429 390 };
430 391
431 392
432 393 /** @method handle_codemirror_keyevent **/
433 394 RawCell.prototype.handle_codemirror_keyevent = function (editor, event) {
434 395
435 396 var that = this;
436 397 if (event.which === key.UPARROW && event.type === 'keydown') {
437 398 // If we are not at the top, let CM handle the up arrow and
438 399 // prevent the global keydown handler from handling it.
439 400 if (!that.at_top()) {
440 401 event.stop();
441 402 return false;
442 403 } else {
443 404 return true;
444 405 };
445 406 } else if (event.which === key.DOWNARROW && event.type === 'keydown') {
446 407 // If we are not at the bottom, let CM handle the down arrow and
447 408 // prevent the global keydown handler from handling it.
448 409 if (!that.at_bottom()) {
449 410 event.stop();
450 411 return false;
451 412 } else {
452 413 return true;
453 414 };
454 415 };
455 416 return false;
456 417 };
457 418
458 419 /** @method select **/
459 420 RawCell.prototype.select = function () {
460 421 IPython.Cell.prototype.select.apply(this);
461 422 this.code_mirror.refresh();
462 423 this.code_mirror.focus();
463 424 };
464 425
465 426 /** @method at_top **/
466 427 RawCell.prototype.at_top = function () {
467 428 var cursor = this.code_mirror.getCursor();
468 429 if (cursor.line === 0 && cursor.ch === 0) {
469 430 return true;
470 431 } else {
471 432 return false;
472 433 }
473 434 };
474 435
475 436
476 437 /** @method at_bottom **/
477 438 RawCell.prototype.at_bottom = function () {
478 439 var cursor = this.code_mirror.getCursor();
479 440 if (cursor.line === (this.code_mirror.lineCount()-1) && cursor.ch === this.code_mirror.getLine(cursor.line).length) {
480 441 return true;
481 442 } else {
482 443 return false;
483 444 }
484 445 };
485 446
486 447
487 448 /**
488 449 * @class HeadingCell
489 450 * @extends Ipython.TextCell
490 451 */
491 452
492 453 /**
493 454 * @constructor HeadingCell
494 455 * @extends Ipython.TextCell
495 456 */
496 457 var HeadingCell = function (options) {
497 458
498 459 options = this.mergeopt(HeadingCell,options)
499 460 TextCell.apply(this, [options]);
500 461
501 462 /**
502 463 * heading level of the cell, use getter and setter to access
503 464 * @property level
504 465 */
505 466 this.level = 1;
506 467 this.cell_type = 'heading';
507 468 };
508 469
509 470 HeadingCell.options_default = {
510 471 placeholder: "Type Heading Here"
511 472 };
512 473
513 474 HeadingCell.prototype = new TextCell();
514 475
515 476 /** @method fromJSON */
516 477 HeadingCell.prototype.fromJSON = function (data) {
517 478 if (data.level != undefined){
518 479 this.level = data.level;
519 480 }
520 481 TextCell.prototype.fromJSON.apply(this, arguments);
521 482 };
522 483
523 484
524 485 /** @method toJSON */
525 486 HeadingCell.prototype.toJSON = function () {
526 487 var data = TextCell.prototype.toJSON.apply(this);
527 488 data.level = this.get_level();
528 489 return data;
529 490 };
530 491
531 492
532 493 /**
533 494 * Change heading level of cell, and re-render
534 495 * @method set_level
535 496 */
536 497 HeadingCell.prototype.set_level = function (level) {
537 498 this.level = level;
538 499 if (this.rendered) {
539 500 this.rendered = false;
540 501 this.render();
541 502 };
542 503 };
543 504
544 505 /** The depth of header cell, based on html (h1 to h6)
545 506 * @method get_level
546 507 * @return {integer} level - for 1 to 6
547 508 */
548 509 HeadingCell.prototype.get_level = function () {
549 510 return this.level;
550 511 };
551 512
552 513
553 514 HeadingCell.prototype.set_rendered = function (text) {
554 515 var r = this.element.find("div.text_cell_render");
555 516 r.empty();
556 517 var link = text.replace(/ /g, '_');
557 518 r.append(
558 519 $('<h'+this.level+'/>')
559 520 .append(
560 521 $('<a/>')
561 522 .addClass('heading-anchor')
562 523 .attr('href', '#' + link)
563 524 .attr('id', link)
564 525 .html(text)
565 526 )
566 527 );
567 528 };
568 529
569 530
570 531 HeadingCell.prototype.get_rendered = function () {
571 532 var r = this.element.find("div.text_cell_render");
572 533 return r.children().first().html();
573 534 };
574 535
575 536
576 537 HeadingCell.prototype.render = function () {
577 538 if (this.rendered === false) {
578 539 var text = this.get_text();
579 540 if (text === "") { text = this.placeholder; }
580 541 this.set_rendered(text);
581 542 this.typeset();
582 543 this.element.find('div.text_cell_input').hide();
583 544 this.element.find("div.text_cell_render").show();
584 545 this.rendered = true;
585 546 };
586 547 };
587 548
588 549 IPython.TextCell = TextCell;
589 IPython.HTMLCell = HTMLCell;
590 550 IPython.MarkdownCell = MarkdownCell;
591 551 IPython.RawCell = RawCell;
592 552 IPython.HeadingCell = HeadingCell;
593 553
594 554
595 555 return IPython;
596 556
597 557 }(IPython));
598 558
General Comments 0
You need to be logged in to leave comments. Login now