##// END OF EJS Templates
Merge pull request #2400 from Carreau/scroll_to_cell...
Bussonnier Matthias -
r8434:a52d09c8 merge
parent child Browse files
Show More
@@ -1,1295 +1,1305 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // Notebook
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13
14 14 var utils = IPython.utils;
15 15 var key = IPython.utils.keycodes;
16 16
17 17 var Notebook = function (selector) {
18 18 this.read_only = IPython.read_only;
19 19 this.element = $(selector);
20 20 this.element.scroll();
21 21 this.element.data("notebook", this);
22 22 this.next_prompt_number = 1;
23 23 this.kernel = null;
24 24 this.clipboard = null;
25 25 this.paste_enabled = false;
26 26 this.dirty = false;
27 27 this.metadata = {};
28 28 // single worksheet for now
29 29 this.worksheet_metadata = {};
30 30 this.control_key_active = false;
31 31 this.notebook_id = null;
32 32 this.notebook_name = null;
33 33 this.notebook_name_blacklist_re = /[\/\\:]/;
34 34 this.nbformat = 3 // Increment this when changing the nbformat
35 35 this.nbformat_minor = 0 // Increment this when changing the nbformat
36 36 this.style();
37 37 this.create_elements();
38 38 this.bind_events();
39 39 };
40 40
41 41
42 42 Notebook.prototype.style = function () {
43 43 $('div#notebook').addClass('border-box-sizing');
44 44 };
45 45
46 46
47 47 Notebook.prototype.create_elements = function () {
48 48 // We add this end_space div to the end of the notebook div to:
49 49 // i) provide a margin between the last cell and the end of the notebook
50 50 // ii) to prevent the div from scrolling up when the last cell is being
51 51 // edited, but is too low on the page, which browsers will do automatically.
52 52 var that = this;
53 53 var end_space = $('<div/>').addClass('end_space').height("30%");
54 54 end_space.dblclick(function (e) {
55 55 if (that.read_only) return;
56 56 var ncells = that.ncells();
57 57 that.insert_cell_below('code',ncells-1);
58 58 });
59 59 this.element.append(end_space);
60 60 $('div#notebook').addClass('border-box-sizing');
61 61 };
62 62
63 63
64 64 Notebook.prototype.bind_events = function () {
65 65 var that = this;
66 66
67 67 $([IPython.events]).on('set_next_input.Notebook', function (event, data) {
68 68 var index = that.find_cell_index(data.cell);
69 69 var new_cell = that.insert_cell_below('code',index);
70 70 new_cell.set_text(data.text);
71 71 that.dirty = true;
72 72 });
73 73
74 74 $([IPython.events]).on('set_dirty.Notebook', function (event, data) {
75 75 that.dirty = data.value;
76 76 });
77 77
78 78 $([IPython.events]).on('select.Cell', function (event, data) {
79 79 var index = that.find_cell_index(data.cell);
80 80 that.select(index);
81 81 });
82 82
83 83
84 84 $(document).keydown(function (event) {
85 85 // console.log(event);
86 86 if (that.read_only) return true;
87 87
88 88 // Save (CTRL+S) or (AppleKey+S)
89 89 //metaKey = applekey on mac
90 90 if ((event.ctrlKey || event.metaKey) && event.keyCode==83) {
91 91 that.save_notebook();
92 92 event.preventDefault();
93 93 return false;
94 94 } else if (event.which === key.ESC) {
95 95 // Intercept escape at highest level to avoid closing
96 96 // websocket connection with firefox
97 97 event.preventDefault();
98 98 } else if (event.which === key.SHIFT) {
99 99 // ignore shift keydown
100 100 return true;
101 101 }
102 102 if (event.which === key.UPARROW && !event.shiftKey) {
103 103 var cell = that.get_selected_cell();
104 104 if (cell.at_top()) {
105 105 event.preventDefault();
106 106 that.select_prev();
107 107 };
108 108 } else if (event.which === key.DOWNARROW && !event.shiftKey) {
109 109 var cell = that.get_selected_cell();
110 110 if (cell.at_bottom()) {
111 111 event.preventDefault();
112 112 that.select_next();
113 113 };
114 114 } else if (event.which === key.ENTER && event.shiftKey) {
115 115 that.execute_selected_cell();
116 116 return false;
117 117 } else if (event.which === key.ENTER && event.altKey) {
118 118 // Execute code cell, and insert new in place
119 119 that.execute_selected_cell();
120 120 // Only insert a new cell, if we ended up in an already populated cell
121 121 if (/\S/.test(that.get_selected_cell().get_text()) == true) {
122 122 that.insert_cell_above('code');
123 123 }
124 124 return false;
125 125 } else if (event.which === key.ENTER && event.ctrlKey) {
126 126 that.execute_selected_cell({terminal:true});
127 127 return false;
128 128 } else if (event.which === 77 && event.ctrlKey && that.control_key_active == false) {
129 129 that.control_key_active = true;
130 130 return false;
131 131 } else if (event.which === 88 && that.control_key_active) {
132 132 // Cut selected cell = x
133 133 that.cut_cell();
134 134 that.control_key_active = false;
135 135 return false;
136 136 } else if (event.which === 67 && that.control_key_active) {
137 137 // Copy selected cell = c
138 138 that.copy_cell();
139 139 that.control_key_active = false;
140 140 return false;
141 141 } else if (event.which === 86 && that.control_key_active) {
142 142 // Paste selected cell = v
143 143 that.paste_cell();
144 144 that.control_key_active = false;
145 145 return false;
146 146 } else if (event.which === 68 && that.control_key_active) {
147 147 // Delete selected cell = d
148 148 that.delete_cell();
149 149 that.control_key_active = false;
150 150 return false;
151 151 } else if (event.which === 65 && that.control_key_active) {
152 152 // Insert code cell above selected = a
153 153 that.insert_cell_above('code');
154 154 that.control_key_active = false;
155 155 return false;
156 156 } else if (event.which === 66 && that.control_key_active) {
157 157 // Insert code cell below selected = b
158 158 that.insert_cell_below('code');
159 159 that.control_key_active = false;
160 160 return false;
161 161 } else if (event.which === 89 && that.control_key_active) {
162 162 // To code = y
163 163 that.to_code();
164 164 that.control_key_active = false;
165 165 return false;
166 166 } else if (event.which === 77 && that.control_key_active) {
167 167 // To markdown = m
168 168 that.to_markdown();
169 169 that.control_key_active = false;
170 170 return false;
171 171 } else if (event.which === 84 && that.control_key_active) {
172 172 // To Raw = t
173 173 that.to_raw();
174 174 that.control_key_active = false;
175 175 return false;
176 176 } else if (event.which === 49 && that.control_key_active) {
177 177 // To Heading 1 = 1
178 178 that.to_heading(undefined, 1);
179 179 that.control_key_active = false;
180 180 return false;
181 181 } else if (event.which === 50 && that.control_key_active) {
182 182 // To Heading 2 = 2
183 183 that.to_heading(undefined, 2);
184 184 that.control_key_active = false;
185 185 return false;
186 186 } else if (event.which === 51 && that.control_key_active) {
187 187 // To Heading 3 = 3
188 188 that.to_heading(undefined, 3);
189 189 that.control_key_active = false;
190 190 return false;
191 191 } else if (event.which === 52 && that.control_key_active) {
192 192 // To Heading 4 = 4
193 193 that.to_heading(undefined, 4);
194 194 that.control_key_active = false;
195 195 return false;
196 196 } else if (event.which === 53 && that.control_key_active) {
197 197 // To Heading 5 = 5
198 198 that.to_heading(undefined, 5);
199 199 that.control_key_active = false;
200 200 return false;
201 201 } else if (event.which === 54 && that.control_key_active) {
202 202 // To Heading 6 = 6
203 203 that.to_heading(undefined, 6);
204 204 that.control_key_active = false;
205 205 return false;
206 206 } else if (event.which === 79 && that.control_key_active) {
207 207 // Toggle output = o
208 208 if (event.shiftKey){
209 209 that.toggle_output_scroll();
210 210 } else {
211 211 that.toggle_output();
212 212 }
213 213 that.control_key_active = false;
214 214 return false;
215 215 } else if (event.which === 83 && that.control_key_active) {
216 216 // Save notebook = s
217 217 that.save_notebook();
218 218 that.control_key_active = false;
219 219 return false;
220 220 } else if (event.which === 74 && that.control_key_active) {
221 221 // Move cell down = j
222 222 that.move_cell_down();
223 223 that.control_key_active = false;
224 224 return false;
225 225 } else if (event.which === 75 && that.control_key_active) {
226 226 // Move cell up = k
227 227 that.move_cell_up();
228 228 that.control_key_active = false;
229 229 return false;
230 230 } else if (event.which === 80 && that.control_key_active) {
231 231 // Select previous = p
232 232 that.select_prev();
233 233 that.control_key_active = false;
234 234 return false;
235 235 } else if (event.which === 78 && that.control_key_active) {
236 236 // Select next = n
237 237 that.select_next();
238 238 that.control_key_active = false;
239 239 return false;
240 240 } else if (event.which === 76 && that.control_key_active) {
241 241 // Toggle line numbers = l
242 242 that.cell_toggle_line_numbers();
243 243 that.control_key_active = false;
244 244 return false;
245 245 } else if (event.which === 73 && that.control_key_active) {
246 246 // Interrupt kernel = i
247 247 that.kernel.interrupt();
248 248 that.control_key_active = false;
249 249 return false;
250 250 } else if (event.which === 190 && that.control_key_active) {
251 251 // Restart kernel = . # matches qt console
252 252 that.restart_kernel();
253 253 that.control_key_active = false;
254 254 return false;
255 255 } else if (event.which === 72 && that.control_key_active) {
256 256 // Show keyboard shortcuts = h
257 257 IPython.quick_help.show_keyboard_shortcuts();
258 258 that.control_key_active = false;
259 259 return false;
260 260 } else if (that.control_key_active) {
261 261 that.control_key_active = false;
262 262 return true;
263 263 };
264 264 return true;
265 265 });
266 266
267 267 var collapse_time = function(time){
268 268 var app_height = $('div#main_app').height(); // content height
269 269 var splitter_height = $('div#pager_splitter').outerHeight(true);
270 270 var new_height = app_height - splitter_height;
271 271 that.element.animate({height : new_height + 'px'}, time);
272 272 }
273 273
274 274 this.element.bind('collapse_pager', function (event,extrap) {
275 275 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
276 276 collapse_time(time);
277 277 });
278 278
279 279 var expand_time = function(time) {
280 280 var app_height = $('div#main_app').height(); // content height
281 281 var splitter_height = $('div#pager_splitter').outerHeight(true);
282 282 var pager_height = $('div#pager').outerHeight(true);
283 283 var new_height = app_height - pager_height - splitter_height;
284 284 that.element.animate({height : new_height + 'px'}, time);
285 285 }
286 286
287 287 this.element.bind('expand_pager', function (event, extrap) {
288 288 time = (extrap != undefined) ? ((extrap.duration != undefined ) ? extrap.duration : 'fast') : 'fast';
289 289 expand_time(time);
290 290 });
291 291
292 292 $(window).bind('beforeunload', function () {
293 293 // TODO: Make killing the kernel configurable.
294 294 var kill_kernel = false;
295 295 if (kill_kernel) {
296 296 that.kernel.kill();
297 297 }
298 298 if (that.dirty && ! that.read_only) {
299 299 return "You have unsaved changes that will be lost if you leave this page.";
300 300 };
301 301 // Null is the *only* return value that will make the browser not
302 302 // pop up the "don't leave" dialog.
303 303 return null;
304 304 });
305 305 };
306 306
307 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
308 var cells = this.get_cells();
309 var time = time || 0;
310 cell_number = Math.min(cells.length-1,cell_number);
311 cell_number = Math.max(0 ,cell_number);
312 scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
313 this.element.animate({scrollTop:scroll_value}, time);
314 return scroll_value;
315 };
316
307 317
308 318 Notebook.prototype.scroll_to_bottom = function () {
309 319 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
310 320 };
311 321
312 322
313 323 Notebook.prototype.scroll_to_top = function () {
314 324 this.element.animate({scrollTop:0}, 0);
315 325 };
316 326
317 327
318 328 // Cell indexing, retrieval, etc.
319 329
320 330 Notebook.prototype.get_cell_elements = function () {
321 331 return this.element.children("div.cell");
322 332 };
323 333
324 334
325 335 Notebook.prototype.get_cell_element = function (index) {
326 336 var result = null;
327 337 var e = this.get_cell_elements().eq(index);
328 338 if (e.length !== 0) {
329 339 result = e;
330 340 }
331 341 return result;
332 342 };
333 343
334 344
335 345 Notebook.prototype.ncells = function (cell) {
336 346 return this.get_cell_elements().length;
337 347 };
338 348
339 349
340 350 // TODO: we are often calling cells as cells()[i], which we should optimize
341 351 // to cells(i) or a new method.
342 352 Notebook.prototype.get_cells = function () {
343 353 return this.get_cell_elements().toArray().map(function (e) {
344 354 return $(e).data("cell");
345 355 });
346 356 };
347 357
348 358
349 359 Notebook.prototype.get_cell = function (index) {
350 360 var result = null;
351 361 var ce = this.get_cell_element(index);
352 362 if (ce !== null) {
353 363 result = ce.data('cell');
354 364 }
355 365 return result;
356 366 }
357 367
358 368
359 369 Notebook.prototype.get_next_cell = function (cell) {
360 370 var result = null;
361 371 var index = this.find_cell_index(cell);
362 372 if (index !== null && index < this.ncells()) {
363 373 result = this.get_cell(index+1);
364 374 }
365 375 return result;
366 376 }
367 377
368 378
369 379 Notebook.prototype.get_prev_cell = function (cell) {
370 380 var result = null;
371 381 var index = this.find_cell_index(cell);
372 382 if (index !== null && index > 1) {
373 383 result = this.get_cell(index-1);
374 384 }
375 385 return result;
376 386 }
377 387
378 388 Notebook.prototype.find_cell_index = function (cell) {
379 389 var result = null;
380 390 this.get_cell_elements().filter(function (index) {
381 391 if ($(this).data("cell") === cell) {
382 392 result = index;
383 393 };
384 394 });
385 395 return result;
386 396 };
387 397
388 398
389 399 Notebook.prototype.index_or_selected = function (index) {
390 400 var i;
391 401 if (index === undefined || index === null) {
392 402 i = this.get_selected_index();
393 403 if (i === null) {
394 404 i = 0;
395 405 }
396 406 } else {
397 407 i = index;
398 408 }
399 409 return i;
400 410 };
401 411
402 412
403 413 Notebook.prototype.get_selected_cell = function () {
404 414 var index = this.get_selected_index();
405 415 return this.get_cell(index);
406 416 };
407 417
408 418
409 419 Notebook.prototype.is_valid_cell_index = function (index) {
410 420 if (index !== null && index >= 0 && index < this.ncells()) {
411 421 return true;
412 422 } else {
413 423 return false;
414 424 };
415 425 }
416 426
417 427 Notebook.prototype.get_selected_index = function () {
418 428 var result = null;
419 429 this.get_cell_elements().filter(function (index) {
420 430 if ($(this).data("cell").selected === true) {
421 431 result = index;
422 432 };
423 433 });
424 434 return result;
425 435 };
426 436
427 437
428 438 // Cell selection.
429 439
430 440 Notebook.prototype.select = function (index) {
431 441 if (index !== undefined && index >= 0 && index < this.ncells()) {
432 442 sindex = this.get_selected_index()
433 443 if (sindex !== null && index !== sindex) {
434 444 this.get_cell(sindex).unselect();
435 445 };
436 446 var cell = this.get_cell(index)
437 447 cell.select();
438 448 if (cell.cell_type === 'heading') {
439 449 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
440 450 {'cell_type':cell.cell_type,level:cell.level}
441 451 );
442 452 } else {
443 453 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
444 454 {'cell_type':cell.cell_type}
445 455 );
446 456 };
447 457 };
448 458 return this;
449 459 };
450 460
451 461
452 462 Notebook.prototype.select_next = function () {
453 463 var index = this.get_selected_index();
454 464 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
455 465 this.select(index+1);
456 466 };
457 467 return this;
458 468 };
459 469
460 470
461 471 Notebook.prototype.select_prev = function () {
462 472 var index = this.get_selected_index();
463 473 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
464 474 this.select(index-1);
465 475 };
466 476 return this;
467 477 };
468 478
469 479
470 480 // Cell movement
471 481
472 482 Notebook.prototype.move_cell_up = function (index) {
473 483 var i = this.index_or_selected();
474 484 if (i !== null && i < this.ncells() && i > 0) {
475 485 var pivot = this.get_cell_element(i-1);
476 486 var tomove = this.get_cell_element(i);
477 487 if (pivot !== null && tomove !== null) {
478 488 tomove.detach();
479 489 pivot.before(tomove);
480 490 this.select(i-1);
481 491 };
482 492 };
483 493 this.dirty = true;
484 494 return this;
485 495 };
486 496
487 497
488 498 Notebook.prototype.move_cell_down = function (index) {
489 499 var i = this.index_or_selected();
490 500 if (i !== null && i < (this.ncells()-1) && i >= 0) {
491 501 var pivot = this.get_cell_element(i+1);
492 502 var tomove = this.get_cell_element(i);
493 503 if (pivot !== null && tomove !== null) {
494 504 tomove.detach();
495 505 pivot.after(tomove);
496 506 this.select(i+1);
497 507 };
498 508 };
499 509 this.dirty = true;
500 510 return this;
501 511 };
502 512
503 513
504 514 Notebook.prototype.sort_cells = function () {
505 515 // This is not working right now. Calling this will actually crash
506 516 // the browser. I think there is an infinite loop in here...
507 517 var ncells = this.ncells();
508 518 var sindex = this.get_selected_index();
509 519 var swapped;
510 520 do {
511 521 swapped = false;
512 522 for (var i=1; i<ncells; i++) {
513 523 current = this.get_cell(i);
514 524 previous = this.get_cell(i-1);
515 525 if (previous.input_prompt_number > current.input_prompt_number) {
516 526 this.move_cell_up(i);
517 527 swapped = true;
518 528 };
519 529 };
520 530 } while (swapped);
521 531 this.select(sindex);
522 532 return this;
523 533 };
524 534
525 535 // Insertion, deletion.
526 536
527 537 Notebook.prototype.delete_cell = function (index) {
528 538 var i = this.index_or_selected(index);
529 539 if (this.is_valid_cell_index(i)) {
530 540 var ce = this.get_cell_element(i);
531 541 ce.remove();
532 542 if (i === (this.ncells())) {
533 543 this.select(i-1);
534 544 } else {
535 545 this.select(i);
536 546 };
537 547 this.dirty = true;
538 548 };
539 549 return this;
540 550 };
541 551
542 552
543 553 Notebook.prototype.insert_cell_below = function (type, index) {
544 554 // type = ('code','html','markdown')
545 555 // index = cell index or undefined to insert below selected
546 556 index = this.index_or_selected(index);
547 557 var cell = null;
548 558 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
549 559 if (type === 'code') {
550 560 cell = new IPython.CodeCell(this.kernel);
551 561 cell.set_input_prompt();
552 562 } else if (type === 'markdown') {
553 563 cell = new IPython.MarkdownCell();
554 564 } else if (type === 'html') {
555 565 cell = new IPython.HTMLCell();
556 566 } else if (type === 'raw') {
557 567 cell = new IPython.RawCell();
558 568 } else if (type === 'heading') {
559 569 cell = new IPython.HeadingCell();
560 570 };
561 571 if (cell !== null) {
562 572 if (this.ncells() === 0) {
563 573 this.element.find('div.end_space').before(cell.element);
564 574 } else if (this.is_valid_cell_index(index)) {
565 575 this.get_cell_element(index).after(cell.element);
566 576 };
567 577 cell.render();
568 578 this.select(this.find_cell_index(cell));
569 579 this.dirty = true;
570 580 return cell;
571 581 };
572 582 };
573 583 return cell;
574 584 };
575 585
576 586
577 587 Notebook.prototype.insert_cell_above = function (type, index) {
578 588 // type = ('code','html','markdown')
579 589 // index = cell index or undefined to insert above selected
580 590 index = this.index_or_selected(index);
581 591 var cell = null;
582 592 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
583 593 if (type === 'code') {
584 594 cell = new IPython.CodeCell(this.kernel);
585 595 cell.set_input_prompt();
586 596 } else if (type === 'markdown') {
587 597 cell = new IPython.MarkdownCell();
588 598 } else if (type === 'html') {
589 599 cell = new IPython.HTMLCell();
590 600 } else if (type === 'raw') {
591 601 cell = new IPython.RawCell();
592 602 } else if (type === 'heading') {
593 603 cell = new IPython.HeadingCell();
594 604 };
595 605 if (cell !== null) {
596 606 if (this.ncells() === 0) {
597 607 this.element.find('div.end_space').before(cell.element);
598 608 } else if (this.is_valid_cell_index(index)) {
599 609 this.get_cell_element(index).before(cell.element);
600 610 };
601 611 cell.render();
602 612 this.select(this.find_cell_index(cell));
603 613 this.dirty = true;
604 614 return cell;
605 615 };
606 616 };
607 617 return cell;
608 618 };
609 619
610 620
611 621 Notebook.prototype.to_code = function (index) {
612 622 var i = this.index_or_selected(index);
613 623 if (this.is_valid_cell_index(i)) {
614 624 var source_element = this.get_cell_element(i);
615 625 var source_cell = source_element.data("cell");
616 626 if (!(source_cell instanceof IPython.CodeCell)) {
617 627 target_cell = this.insert_cell_below('code',i);
618 628 var text = source_cell.get_text();
619 629 if (text === source_cell.placeholder) {
620 630 text = '';
621 631 }
622 632 target_cell.set_text(text);
623 633 // make this value the starting point, so that we can only undo
624 634 // to this state, instead of a blank cell
625 635 target_cell.code_mirror.clearHistory();
626 636 source_element.remove();
627 637 this.dirty = true;
628 638 };
629 639 };
630 640 };
631 641
632 642
633 643 Notebook.prototype.to_markdown = function (index) {
634 644 var i = this.index_or_selected(index);
635 645 if (this.is_valid_cell_index(i)) {
636 646 var source_element = this.get_cell_element(i);
637 647 var source_cell = source_element.data("cell");
638 648 if (!(source_cell instanceof IPython.MarkdownCell)) {
639 649 target_cell = this.insert_cell_below('markdown',i);
640 650 var text = source_cell.get_text();
641 651 if (text === source_cell.placeholder) {
642 652 text = '';
643 653 };
644 654 // The edit must come before the set_text.
645 655 target_cell.edit();
646 656 target_cell.set_text(text);
647 657 // make this value the starting point, so that we can only undo
648 658 // to this state, instead of a blank cell
649 659 target_cell.code_mirror.clearHistory();
650 660 source_element.remove();
651 661 this.dirty = true;
652 662 };
653 663 };
654 664 };
655 665
656 666
657 667 Notebook.prototype.to_html = function (index) {
658 668 var i = this.index_or_selected(index);
659 669 if (this.is_valid_cell_index(i)) {
660 670 var source_element = this.get_cell_element(i);
661 671 var source_cell = source_element.data("cell");
662 672 var target_cell = null;
663 673 if (!(source_cell instanceof IPython.HTMLCell)) {
664 674 target_cell = this.insert_cell_below('html',i);
665 675 var text = source_cell.get_text();
666 676 if (text === source_cell.placeholder) {
667 677 text = '';
668 678 };
669 679 // The edit must come before the set_text.
670 680 target_cell.edit();
671 681 target_cell.set_text(text);
672 682 // make this value the starting point, so that we can only undo
673 683 // to this state, instead of a blank cell
674 684 target_cell.code_mirror.clearHistory();
675 685 source_element.remove();
676 686 this.dirty = true;
677 687 };
678 688 };
679 689 };
680 690
681 691
682 692 Notebook.prototype.to_raw = function (index) {
683 693 var i = this.index_or_selected(index);
684 694 if (this.is_valid_cell_index(i)) {
685 695 var source_element = this.get_cell_element(i);
686 696 var source_cell = source_element.data("cell");
687 697 var target_cell = null;
688 698 if (!(source_cell instanceof IPython.RawCell)) {
689 699 target_cell = this.insert_cell_below('raw',i);
690 700 var text = source_cell.get_text();
691 701 if (text === source_cell.placeholder) {
692 702 text = '';
693 703 };
694 704 // The edit must come before the set_text.
695 705 target_cell.edit();
696 706 target_cell.set_text(text);
697 707 // make this value the starting point, so that we can only undo
698 708 // to this state, instead of a blank cell
699 709 target_cell.code_mirror.clearHistory();
700 710 source_element.remove();
701 711 this.dirty = true;
702 712 };
703 713 };
704 714 };
705 715
706 716
707 717 Notebook.prototype.to_heading = function (index, level) {
708 718 level = level || 1;
709 719 var i = this.index_or_selected(index);
710 720 if (this.is_valid_cell_index(i)) {
711 721 var source_element = this.get_cell_element(i);
712 722 var source_cell = source_element.data("cell");
713 723 var target_cell = null;
714 724 if (source_cell instanceof IPython.HeadingCell) {
715 725 source_cell.set_level(level);
716 726 } else {
717 727 target_cell = this.insert_cell_below('heading',i);
718 728 var text = source_cell.get_text();
719 729 if (text === source_cell.placeholder) {
720 730 text = '';
721 731 };
722 732 // The edit must come before the set_text.
723 733 target_cell.set_level(level);
724 734 target_cell.edit();
725 735 target_cell.set_text(text);
726 736 // make this value the starting point, so that we can only undo
727 737 // to this state, instead of a blank cell
728 738 target_cell.code_mirror.clearHistory();
729 739 source_element.remove();
730 740 this.dirty = true;
731 741 };
732 742 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
733 743 {'cell_type':'heading',level:level}
734 744 );
735 745 };
736 746 };
737 747
738 748
739 749 // Cut/Copy/Paste
740 750
741 751 Notebook.prototype.enable_paste = function () {
742 752 var that = this;
743 753 if (!this.paste_enabled) {
744 754 $('#paste_cell').removeClass('ui-state-disabled')
745 755 .on('click', function () {that.paste_cell();});
746 756 $('#paste_cell_above').removeClass('ui-state-disabled')
747 757 .on('click', function () {that.paste_cell_above();});
748 758 $('#paste_cell_below').removeClass('ui-state-disabled')
749 759 .on('click', function () {that.paste_cell_below();});
750 760 this.paste_enabled = true;
751 761 };
752 762 };
753 763
754 764
755 765 Notebook.prototype.disable_paste = function () {
756 766 if (this.paste_enabled) {
757 767 $('#paste_cell').addClass('ui-state-disabled').off('click');
758 768 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
759 769 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
760 770 this.paste_enabled = false;
761 771 };
762 772 };
763 773
764 774
765 775 Notebook.prototype.cut_cell = function () {
766 776 this.copy_cell();
767 777 this.delete_cell();
768 778 }
769 779
770 780 Notebook.prototype.copy_cell = function () {
771 781 var cell = this.get_selected_cell();
772 782 this.clipboard = cell.toJSON();
773 783 this.enable_paste();
774 784 };
775 785
776 786
777 787 Notebook.prototype.paste_cell = function () {
778 788 if (this.clipboard !== null && this.paste_enabled) {
779 789 var cell_data = this.clipboard;
780 790 var new_cell = this.insert_cell_above(cell_data.cell_type);
781 791 new_cell.fromJSON(cell_data);
782 792 old_cell = this.get_next_cell(new_cell);
783 793 this.delete_cell(this.find_cell_index(old_cell));
784 794 this.select(this.find_cell_index(new_cell));
785 795 };
786 796 };
787 797
788 798
789 799 Notebook.prototype.paste_cell_above = function () {
790 800 if (this.clipboard !== null && this.paste_enabled) {
791 801 var cell_data = this.clipboard;
792 802 var new_cell = this.insert_cell_above(cell_data.cell_type);
793 803 new_cell.fromJSON(cell_data);
794 804 };
795 805 };
796 806
797 807
798 808 Notebook.prototype.paste_cell_below = function () {
799 809 if (this.clipboard !== null && this.paste_enabled) {
800 810 var cell_data = this.clipboard;
801 811 var new_cell = this.insert_cell_below(cell_data.cell_type);
802 812 new_cell.fromJSON(cell_data);
803 813 };
804 814 };
805 815
806 816
807 817 // Split/merge
808 818
809 819 Notebook.prototype.split_cell = function () {
810 820 // Todo: implement spliting for other cell types.
811 821 var cell = this.get_selected_cell();
812 822 if (cell.is_splittable()) {
813 823 texta = cell.get_pre_cursor();
814 824 textb = cell.get_post_cursor();
815 825 if (cell instanceof IPython.CodeCell) {
816 826 cell.set_text(texta);
817 827 var new_cell = this.insert_cell_below('code');
818 828 new_cell.set_text(textb);
819 829 } else if (cell instanceof IPython.MarkdownCell) {
820 830 cell.set_text(texta);
821 831 cell.render();
822 832 var new_cell = this.insert_cell_below('markdown');
823 833 new_cell.edit(); // editor must be visible to call set_text
824 834 new_cell.set_text(textb);
825 835 new_cell.render();
826 836 } else if (cell instanceof IPython.HTMLCell) {
827 837 cell.set_text(texta);
828 838 cell.render();
829 839 var new_cell = this.insert_cell_below('html');
830 840 new_cell.edit(); // editor must be visible to call set_text
831 841 new_cell.set_text(textb);
832 842 new_cell.render();
833 843 };
834 844 };
835 845 };
836 846
837 847
838 848 Notebook.prototype.merge_cell_above = function () {
839 849 var index = this.get_selected_index();
840 850 var cell = this.get_cell(index);
841 851 if (index > 0) {
842 852 upper_cell = this.get_cell(index-1);
843 853 upper_text = upper_cell.get_text();
844 854 text = cell.get_text();
845 855 if (cell instanceof IPython.CodeCell) {
846 856 cell.set_text(upper_text+'\n'+text);
847 857 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
848 858 cell.edit();
849 859 cell.set_text(upper_text+'\n'+text);
850 860 cell.render();
851 861 };
852 862 this.delete_cell(index-1);
853 863 this.select(this.find_cell_index(cell));
854 864 };
855 865 };
856 866
857 867
858 868 Notebook.prototype.merge_cell_below = function () {
859 869 var index = this.get_selected_index();
860 870 var cell = this.get_cell(index);
861 871 if (index < this.ncells()-1) {
862 872 lower_cell = this.get_cell(index+1);
863 873 lower_text = lower_cell.get_text();
864 874 text = cell.get_text();
865 875 if (cell instanceof IPython.CodeCell) {
866 876 cell.set_text(text+'\n'+lower_text);
867 877 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
868 878 cell.edit();
869 879 cell.set_text(text+'\n'+lower_text);
870 880 cell.render();
871 881 };
872 882 this.delete_cell(index+1);
873 883 this.select(this.find_cell_index(cell));
874 884 };
875 885 };
876 886
877 887
878 888 // Cell collapsing and output clearing
879 889
880 890 Notebook.prototype.collapse = function (index) {
881 891 var i = this.index_or_selected(index);
882 892 this.get_cell(i).collapse();
883 893 this.dirty = true;
884 894 };
885 895
886 896
887 897 Notebook.prototype.expand = function (index) {
888 898 var i = this.index_or_selected(index);
889 899 this.get_cell(i).expand();
890 900 this.dirty = true;
891 901 };
892 902
893 903
894 904 Notebook.prototype.toggle_output = function (index) {
895 905 var i = this.index_or_selected(index);
896 906 this.get_cell(i).toggle_output();
897 907 this.dirty = true;
898 908 };
899 909
900 910
901 911 Notebook.prototype.toggle_output_scroll = function (index) {
902 912 var i = this.index_or_selected(index);
903 913 this.get_cell(i).toggle_output_scroll();
904 914 };
905 915
906 916
907 917 Notebook.prototype.collapse_all_output = function () {
908 918 var ncells = this.ncells();
909 919 var cells = this.get_cells();
910 920 for (var i=0; i<ncells; i++) {
911 921 if (cells[i] instanceof IPython.CodeCell) {
912 922 cells[i].output_area.collapse();
913 923 }
914 924 };
915 925 // this should not be set if the `collapse` key is removed from nbformat
916 926 this.dirty = true;
917 927 };
918 928
919 929
920 930 Notebook.prototype.scroll_all_output = function () {
921 931 var ncells = this.ncells();
922 932 var cells = this.get_cells();
923 933 for (var i=0; i<ncells; i++) {
924 934 if (cells[i] instanceof IPython.CodeCell) {
925 935 cells[i].output_area.expand();
926 936 cells[i].output_area.scroll_if_long(20);
927 937 }
928 938 };
929 939 // this should not be set if the `collapse` key is removed from nbformat
930 940 this.dirty = true;
931 941 };
932 942
933 943
934 944 Notebook.prototype.expand_all_output = function () {
935 945 var ncells = this.ncells();
936 946 var cells = this.get_cells();
937 947 for (var i=0; i<ncells; i++) {
938 948 if (cells[i] instanceof IPython.CodeCell) {
939 949 cells[i].output_area.expand();
940 950 cells[i].output_area.unscroll_area();
941 951 }
942 952 };
943 953 // this should not be set if the `collapse` key is removed from nbformat
944 954 this.dirty = true;
945 955 };
946 956
947 957
948 958 Notebook.prototype.clear_all_output = function () {
949 959 var ncells = this.ncells();
950 960 var cells = this.get_cells();
951 961 for (var i=0; i<ncells; i++) {
952 962 if (cells[i] instanceof IPython.CodeCell) {
953 963 cells[i].clear_output(true,true,true);
954 964 // Make all In[] prompts blank, as well
955 965 // TODO: make this configurable (via checkbox?)
956 966 cells[i].set_input_prompt();
957 967 }
958 968 };
959 969 this.dirty = true;
960 970 };
961 971
962 972
963 973 // Other cell functions: line numbers, ...
964 974
965 975 Notebook.prototype.cell_toggle_line_numbers = function() {
966 976 this.get_selected_cell().toggle_line_numbers();
967 977 };
968 978
969 979 // Kernel related things
970 980
971 981 Notebook.prototype.start_kernel = function () {
972 982 var base_url = $('body').data('baseKernelUrl') + "kernels";
973 983 this.kernel = new IPython.Kernel(base_url);
974 984 this.kernel.start(this.notebook_id);
975 985 // Now that the kernel has been created, tell the CodeCells about it.
976 986 var ncells = this.ncells();
977 987 for (var i=0; i<ncells; i++) {
978 988 var cell = this.get_cell(i);
979 989 if (cell instanceof IPython.CodeCell) {
980 990 cell.set_kernel(this.kernel)
981 991 };
982 992 };
983 993 };
984 994
985 995
986 996 Notebook.prototype.restart_kernel = function () {
987 997 var that = this;
988 998 var dialog = $('<div/>');
989 999 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
990 1000 $(document).append(dialog);
991 1001 dialog.dialog({
992 1002 resizable: false,
993 1003 modal: true,
994 1004 title: "Restart kernel or continue running?",
995 1005 closeText: '',
996 1006 buttons : {
997 1007 "Restart": function () {
998 1008 that.kernel.restart();
999 1009 $(this).dialog('close');
1000 1010 },
1001 1011 "Continue running": function () {
1002 1012 $(this).dialog('close');
1003 1013 }
1004 1014 }
1005 1015 });
1006 1016 };
1007 1017
1008 1018
1009 1019 Notebook.prototype.execute_selected_cell = function (options) {
1010 1020 // add_new: should a new cell be added if we are at the end of the nb
1011 1021 // terminal: execute in terminal mode, which stays in the current cell
1012 1022 default_options = {terminal: false, add_new: true};
1013 1023 $.extend(default_options, options);
1014 1024 var that = this;
1015 1025 var cell = that.get_selected_cell();
1016 1026 var cell_index = that.find_cell_index(cell);
1017 1027 if (cell instanceof IPython.CodeCell) {
1018 1028 cell.execute();
1019 1029 } else if (cell instanceof IPython.HTMLCell) {
1020 1030 cell.render();
1021 1031 }
1022 1032 if (default_options.terminal) {
1023 1033 cell.select_all();
1024 1034 } else {
1025 1035 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1026 1036 that.insert_cell_below('code');
1027 1037 // If we are adding a new cell at the end, scroll down to show it.
1028 1038 that.scroll_to_bottom();
1029 1039 } else {
1030 1040 that.select(cell_index+1);
1031 1041 };
1032 1042 };
1033 1043 this.dirty = true;
1034 1044 };
1035 1045
1036 1046
1037 1047 Notebook.prototype.execute_all_cells = function () {
1038 1048 var ncells = this.ncells();
1039 1049 for (var i=0; i<ncells; i++) {
1040 1050 this.select(i);
1041 1051 this.execute_selected_cell({add_new:false});
1042 1052 };
1043 1053 this.scroll_to_bottom();
1044 1054 };
1045 1055
1046 1056 // Persistance and loading
1047 1057
1048 1058 Notebook.prototype.get_notebook_id = function () {
1049 1059 return this.notebook_id;
1050 1060 };
1051 1061
1052 1062
1053 1063 Notebook.prototype.get_notebook_name = function () {
1054 1064 return this.notebook_name;
1055 1065 };
1056 1066
1057 1067
1058 1068 Notebook.prototype.set_notebook_name = function (name) {
1059 1069 this.notebook_name = name;
1060 1070 };
1061 1071
1062 1072
1063 1073 Notebook.prototype.test_notebook_name = function (nbname) {
1064 1074 nbname = nbname || '';
1065 1075 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1066 1076 return true;
1067 1077 } else {
1068 1078 return false;
1069 1079 };
1070 1080 };
1071 1081
1072 1082
1073 1083 Notebook.prototype.fromJSON = function (data) {
1074 1084 var ncells = this.ncells();
1075 1085 var i;
1076 1086 for (i=0; i<ncells; i++) {
1077 1087 // Always delete cell 0 as they get renumbered as they are deleted.
1078 1088 this.delete_cell(0);
1079 1089 };
1080 1090 // Save the metadata and name.
1081 1091 this.metadata = data.metadata;
1082 1092 this.notebook_name = data.metadata.name;
1083 1093 // Only handle 1 worksheet for now.
1084 1094 var worksheet = data.worksheets[0];
1085 1095 if (worksheet !== undefined) {
1086 1096 if (worksheet.metadata) {
1087 1097 this.worksheet_metadata = worksheet.metadata;
1088 1098 }
1089 1099 var new_cells = worksheet.cells;
1090 1100 ncells = new_cells.length;
1091 1101 var cell_data = null;
1092 1102 var new_cell = null;
1093 1103 for (i=0; i<ncells; i++) {
1094 1104 cell_data = new_cells[i];
1095 1105 // VERSIONHACK: plaintext -> raw
1096 1106 // handle never-released plaintext name for raw cells
1097 1107 if (cell_data.cell_type === 'plaintext'){
1098 1108 cell_data.cell_type = 'raw';
1099 1109 }
1100 1110
1101 1111 new_cell = this.insert_cell_below(cell_data.cell_type);
1102 1112 new_cell.fromJSON(cell_data);
1103 1113 };
1104 1114 };
1105 1115 if (data.worksheets.length > 1) {
1106 1116 var dialog = $('<div/>');
1107 1117 dialog.html("This notebook has " + data.worksheets.length + " worksheets, " +
1108 1118 "but this version of IPython can only handle the first. " +
1109 1119 "If you save this notebook, worksheets after the first will be lost."
1110 1120 );
1111 1121 this.element.append(dialog);
1112 1122 dialog.dialog({
1113 1123 resizable: false,
1114 1124 modal: true,
1115 1125 title: "Multiple worksheets",
1116 1126 closeText: "",
1117 1127 close: function(event, ui) {$(this).dialog('destroy').remove();},
1118 1128 buttons : {
1119 1129 "OK": function () {
1120 1130 $(this).dialog('close');
1121 1131 }
1122 1132 },
1123 1133 width: 400
1124 1134 });
1125 1135 }
1126 1136 };
1127 1137
1128 1138
1129 1139 Notebook.prototype.toJSON = function () {
1130 1140 var cells = this.get_cells();
1131 1141 var ncells = cells.length;
1132 1142 var cell_array = new Array(ncells);
1133 1143 for (var i=0; i<ncells; i++) {
1134 1144 cell_array[i] = cells[i].toJSON();
1135 1145 };
1136 1146 var data = {
1137 1147 // Only handle 1 worksheet for now.
1138 1148 worksheets : [{
1139 1149 cells: cell_array,
1140 1150 metadata: this.worksheet_metadata
1141 1151 }],
1142 1152 metadata : this.metadata
1143 1153 };
1144 1154 return data;
1145 1155 };
1146 1156
1147 1157 Notebook.prototype.save_notebook = function () {
1148 1158 // We may want to move the name/id/nbformat logic inside toJSON?
1149 1159 var data = this.toJSON();
1150 1160 data.metadata.name = this.notebook_name;
1151 1161 data.nbformat = this.nbformat;
1152 1162 data.nbformat_minor = this.nbformat_minor;
1153 1163 // We do the call with settings so we can set cache to false.
1154 1164 var settings = {
1155 1165 processData : false,
1156 1166 cache : false,
1157 1167 type : "PUT",
1158 1168 data : JSON.stringify(data),
1159 1169 headers : {'Content-Type': 'application/json'},
1160 1170 success : $.proxy(this.save_notebook_success,this),
1161 1171 error : $.proxy(this.save_notebook_error,this)
1162 1172 };
1163 1173 $([IPython.events]).trigger('notebook_saving.Notebook');
1164 1174 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1165 1175 $.ajax(url, settings);
1166 1176 };
1167 1177
1168 1178
1169 1179 Notebook.prototype.save_notebook_success = function (data, status, xhr) {
1170 1180 this.dirty = false;
1171 1181 $([IPython.events]).trigger('notebook_saved.Notebook');
1172 1182 };
1173 1183
1174 1184
1175 1185 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1176 1186 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1177 1187 };
1178 1188
1179 1189
1180 1190 Notebook.prototype.load_notebook = function (notebook_id) {
1181 1191 var that = this;
1182 1192 this.notebook_id = notebook_id;
1183 1193 // We do the call with settings so we can set cache to false.
1184 1194 var settings = {
1185 1195 processData : false,
1186 1196 cache : false,
1187 1197 type : "GET",
1188 1198 dataType : "json",
1189 1199 success : $.proxy(this.load_notebook_success,this),
1190 1200 error : $.proxy(this.load_notebook_error,this),
1191 1201 };
1192 1202 $([IPython.events]).trigger('notebook_loading.Notebook');
1193 1203 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1194 1204 $.ajax(url, settings);
1195 1205 };
1196 1206
1197 1207
1198 1208 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1199 1209 this.fromJSON(data);
1200 1210 if (this.ncells() === 0) {
1201 1211 this.insert_cell_below('code');
1202 1212 };
1203 1213 this.dirty = false;
1204 1214 this.select(0);
1205 1215 this.scroll_to_top();
1206 1216 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1207 1217 msg = "This notebook has been converted from an older " +
1208 1218 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1209 1219 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1210 1220 "newer notebook format will be used and older verions of IPython " +
1211 1221 "may not be able to read it. To keep the older version, close the " +
1212 1222 "notebook without saving it.";
1213 1223 var dialog = $('<div/>');
1214 1224 dialog.html(msg);
1215 1225 this.element.append(dialog);
1216 1226 dialog.dialog({
1217 1227 resizable: false,
1218 1228 modal: true,
1219 1229 title: "Notebook converted",
1220 1230 closeText: "",
1221 1231 close: function(event, ui) {$(this).dialog('destroy').remove();},
1222 1232 buttons : {
1223 1233 "OK": function () {
1224 1234 $(this).dialog('close');
1225 1235 }
1226 1236 },
1227 1237 width: 400
1228 1238 });
1229 1239 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1230 1240 var that = this;
1231 1241 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1232 1242 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1233 1243 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1234 1244 this_vs + ". You can still work with this notebook, but some features " +
1235 1245 "introduced in later notebook versions may not be available."
1236 1246
1237 1247 var dialog = $('<div/>');
1238 1248 dialog.html(msg);
1239 1249 this.element.append(dialog);
1240 1250 dialog.dialog({
1241 1251 resizable: false,
1242 1252 modal: true,
1243 1253 title: "Newer Notebook",
1244 1254 closeText: "",
1245 1255 close: function(event, ui) {$(this).dialog('destroy').remove();},
1246 1256 buttons : {
1247 1257 "OK": function () {
1248 1258 $(this).dialog('close');
1249 1259 }
1250 1260 },
1251 1261 width: 400
1252 1262 });
1253 1263
1254 1264 }
1255 1265 // Create the kernel after the notebook is completely loaded to prevent
1256 1266 // code execution upon loading, which is a security risk.
1257 1267 if (! this.read_only) {
1258 1268 this.start_kernel();
1259 1269 }
1260 1270 $([IPython.events]).trigger('notebook_loaded.Notebook');
1261 1271 };
1262 1272
1263 1273
1264 1274 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1265 1275 if (xhr.status === 500) {
1266 1276 msg = "An error occurred while loading this notebook. Most likely " +
1267 1277 "this notebook is in a newer format than is supported by this " +
1268 1278 "version of IPython. This version can load notebook formats " +
1269 1279 "v"+this.nbformat+" or earlier.";
1270 1280 var dialog = $('<div/>');
1271 1281 dialog.html(msg);
1272 1282 this.element.append(dialog);
1273 1283 dialog.dialog({
1274 1284 resizable: false,
1275 1285 modal: true,
1276 1286 title: "Error loading notebook",
1277 1287 closeText: "",
1278 1288 close: function(event, ui) {$(this).dialog('destroy').remove();},
1279 1289 buttons : {
1280 1290 "OK": function () {
1281 1291 $(this).dialog('close');
1282 1292 }
1283 1293 },
1284 1294 width: 400
1285 1295 });
1286 1296 }
1287 1297 }
1288 1298
1289 1299 IPython.Notebook = Notebook;
1290 1300
1291 1301
1292 1302 return IPython;
1293 1303
1294 1304 }(IPython));
1295 1305
General Comments 0
You need to be logged in to leave comments. Login now