##// END OF EJS Templates
Added checking for emptiness of cell below.
v923z -
Show More
@@ -1,1295 +1,1295
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 if (that.get_selected_cell().toJSON().input !== "") {
121 if (/\S/.test(that.get_selected_cell().toJSON().input) == 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 307
308 308 Notebook.prototype.scroll_to_bottom = function () {
309 309 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
310 310 };
311 311
312 312
313 313 Notebook.prototype.scroll_to_top = function () {
314 314 this.element.animate({scrollTop:0}, 0);
315 315 };
316 316
317 317
318 318 // Cell indexing, retrieval, etc.
319 319
320 320 Notebook.prototype.get_cell_elements = function () {
321 321 return this.element.children("div.cell");
322 322 };
323 323
324 324
325 325 Notebook.prototype.get_cell_element = function (index) {
326 326 var result = null;
327 327 var e = this.get_cell_elements().eq(index);
328 328 if (e.length !== 0) {
329 329 result = e;
330 330 }
331 331 return result;
332 332 };
333 333
334 334
335 335 Notebook.prototype.ncells = function (cell) {
336 336 return this.get_cell_elements().length;
337 337 };
338 338
339 339
340 340 // TODO: we are often calling cells as cells()[i], which we should optimize
341 341 // to cells(i) or a new method.
342 342 Notebook.prototype.get_cells = function () {
343 343 return this.get_cell_elements().toArray().map(function (e) {
344 344 return $(e).data("cell");
345 345 });
346 346 };
347 347
348 348
349 349 Notebook.prototype.get_cell = function (index) {
350 350 var result = null;
351 351 var ce = this.get_cell_element(index);
352 352 if (ce !== null) {
353 353 result = ce.data('cell');
354 354 }
355 355 return result;
356 356 }
357 357
358 358
359 359 Notebook.prototype.get_next_cell = function (cell) {
360 360 var result = null;
361 361 var index = this.find_cell_index(cell);
362 362 if (index !== null && index < this.ncells()) {
363 363 result = this.get_cell(index+1);
364 364 }
365 365 return result;
366 366 }
367 367
368 368
369 369 Notebook.prototype.get_prev_cell = function (cell) {
370 370 var result = null;
371 371 var index = this.find_cell_index(cell);
372 372 if (index !== null && index > 1) {
373 373 result = this.get_cell(index-1);
374 374 }
375 375 return result;
376 376 }
377 377
378 378 Notebook.prototype.find_cell_index = function (cell) {
379 379 var result = null;
380 380 this.get_cell_elements().filter(function (index) {
381 381 if ($(this).data("cell") === cell) {
382 382 result = index;
383 383 };
384 384 });
385 385 return result;
386 386 };
387 387
388 388
389 389 Notebook.prototype.index_or_selected = function (index) {
390 390 var i;
391 391 if (index === undefined || index === null) {
392 392 i = this.get_selected_index();
393 393 if (i === null) {
394 394 i = 0;
395 395 }
396 396 } else {
397 397 i = index;
398 398 }
399 399 return i;
400 400 };
401 401
402 402
403 403 Notebook.prototype.get_selected_cell = function () {
404 404 var index = this.get_selected_index();
405 405 return this.get_cell(index);
406 406 };
407 407
408 408
409 409 Notebook.prototype.is_valid_cell_index = function (index) {
410 410 if (index !== null && index >= 0 && index < this.ncells()) {
411 411 return true;
412 412 } else {
413 413 return false;
414 414 };
415 415 }
416 416
417 417 Notebook.prototype.get_selected_index = function () {
418 418 var result = null;
419 419 this.get_cell_elements().filter(function (index) {
420 420 if ($(this).data("cell").selected === true) {
421 421 result = index;
422 422 };
423 423 });
424 424 return result;
425 425 };
426 426
427 427
428 428 // Cell selection.
429 429
430 430 Notebook.prototype.select = function (index) {
431 431 if (index !== undefined && index >= 0 && index < this.ncells()) {
432 432 sindex = this.get_selected_index()
433 433 if (sindex !== null && index !== sindex) {
434 434 this.get_cell(sindex).unselect();
435 435 };
436 436 var cell = this.get_cell(index)
437 437 cell.select();
438 438 if (cell.cell_type === 'heading') {
439 439 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
440 440 {'cell_type':cell.cell_type,level:cell.level}
441 441 );
442 442 } else {
443 443 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
444 444 {'cell_type':cell.cell_type}
445 445 );
446 446 };
447 447 };
448 448 return this;
449 449 };
450 450
451 451
452 452 Notebook.prototype.select_next = function () {
453 453 var index = this.get_selected_index();
454 454 if (index !== null && index >= 0 && (index+1) < this.ncells()) {
455 455 this.select(index+1);
456 456 };
457 457 return this;
458 458 };
459 459
460 460
461 461 Notebook.prototype.select_prev = function () {
462 462 var index = this.get_selected_index();
463 463 if (index !== null && index >= 0 && (index-1) < this.ncells()) {
464 464 this.select(index-1);
465 465 };
466 466 return this;
467 467 };
468 468
469 469
470 470 // Cell movement
471 471
472 472 Notebook.prototype.move_cell_up = function (index) {
473 473 var i = this.index_or_selected();
474 474 if (i !== null && i < this.ncells() && i > 0) {
475 475 var pivot = this.get_cell_element(i-1);
476 476 var tomove = this.get_cell_element(i);
477 477 if (pivot !== null && tomove !== null) {
478 478 tomove.detach();
479 479 pivot.before(tomove);
480 480 this.select(i-1);
481 481 };
482 482 };
483 483 this.dirty = true;
484 484 return this;
485 485 };
486 486
487 487
488 488 Notebook.prototype.move_cell_down = function (index) {
489 489 var i = this.index_or_selected();
490 490 if (i !== null && i < (this.ncells()-1) && i >= 0) {
491 491 var pivot = this.get_cell_element(i+1);
492 492 var tomove = this.get_cell_element(i);
493 493 if (pivot !== null && tomove !== null) {
494 494 tomove.detach();
495 495 pivot.after(tomove);
496 496 this.select(i+1);
497 497 };
498 498 };
499 499 this.dirty = true;
500 500 return this;
501 501 };
502 502
503 503
504 504 Notebook.prototype.sort_cells = function () {
505 505 // This is not working right now. Calling this will actually crash
506 506 // the browser. I think there is an infinite loop in here...
507 507 var ncells = this.ncells();
508 508 var sindex = this.get_selected_index();
509 509 var swapped;
510 510 do {
511 511 swapped = false;
512 512 for (var i=1; i<ncells; i++) {
513 513 current = this.get_cell(i);
514 514 previous = this.get_cell(i-1);
515 515 if (previous.input_prompt_number > current.input_prompt_number) {
516 516 this.move_cell_up(i);
517 517 swapped = true;
518 518 };
519 519 };
520 520 } while (swapped);
521 521 this.select(sindex);
522 522 return this;
523 523 };
524 524
525 525 // Insertion, deletion.
526 526
527 527 Notebook.prototype.delete_cell = function (index) {
528 528 var i = this.index_or_selected(index);
529 529 if (this.is_valid_cell_index(i)) {
530 530 var ce = this.get_cell_element(i);
531 531 ce.remove();
532 532 if (i === (this.ncells())) {
533 533 this.select(i-1);
534 534 } else {
535 535 this.select(i);
536 536 };
537 537 this.dirty = true;
538 538 };
539 539 return this;
540 540 };
541 541
542 542
543 543 Notebook.prototype.insert_cell_below = function (type, index) {
544 544 // type = ('code','html','markdown')
545 545 // index = cell index or undefined to insert below selected
546 546 index = this.index_or_selected(index);
547 547 var cell = null;
548 548 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
549 549 if (type === 'code') {
550 550 cell = new IPython.CodeCell(this.kernel);
551 551 cell.set_input_prompt();
552 552 } else if (type === 'markdown') {
553 553 cell = new IPython.MarkdownCell();
554 554 } else if (type === 'html') {
555 555 cell = new IPython.HTMLCell();
556 556 } else if (type === 'raw') {
557 557 cell = new IPython.RawCell();
558 558 } else if (type === 'heading') {
559 559 cell = new IPython.HeadingCell();
560 560 };
561 561 if (cell !== null) {
562 562 if (this.ncells() === 0) {
563 563 this.element.find('div.end_space').before(cell.element);
564 564 } else if (this.is_valid_cell_index(index)) {
565 565 this.get_cell_element(index).after(cell.element);
566 566 };
567 567 cell.render();
568 568 this.select(this.find_cell_index(cell));
569 569 this.dirty = true;
570 570 return cell;
571 571 };
572 572 };
573 573 return cell;
574 574 };
575 575
576 576
577 577 Notebook.prototype.insert_cell_above = function (type, index) {
578 578 // type = ('code','html','markdown')
579 579 // index = cell index or undefined to insert above selected
580 580 index = this.index_or_selected(index);
581 581 var cell = null;
582 582 if (this.ncells() === 0 || this.is_valid_cell_index(index)) {
583 583 if (type === 'code') {
584 584 cell = new IPython.CodeCell(this.kernel);
585 585 cell.set_input_prompt();
586 586 } else if (type === 'markdown') {
587 587 cell = new IPython.MarkdownCell();
588 588 } else if (type === 'html') {
589 589 cell = new IPython.HTMLCell();
590 590 } else if (type === 'raw') {
591 591 cell = new IPython.RawCell();
592 592 } else if (type === 'heading') {
593 593 cell = new IPython.HeadingCell();
594 594 };
595 595 if (cell !== null) {
596 596 if (this.ncells() === 0) {
597 597 this.element.find('div.end_space').before(cell.element);
598 598 } else if (this.is_valid_cell_index(index)) {
599 599 this.get_cell_element(index).before(cell.element);
600 600 };
601 601 cell.render();
602 602 this.select(this.find_cell_index(cell));
603 603 this.dirty = true;
604 604 return cell;
605 605 };
606 606 };
607 607 return cell;
608 608 };
609 609
610 610
611 611 Notebook.prototype.to_code = function (index) {
612 612 var i = this.index_or_selected(index);
613 613 if (this.is_valid_cell_index(i)) {
614 614 var source_element = this.get_cell_element(i);
615 615 var source_cell = source_element.data("cell");
616 616 if (!(source_cell instanceof IPython.CodeCell)) {
617 617 target_cell = this.insert_cell_below('code',i);
618 618 var text = source_cell.get_text();
619 619 if (text === source_cell.placeholder) {
620 620 text = '';
621 621 }
622 622 target_cell.set_text(text);
623 623 // make this value the starting point, so that we can only undo
624 624 // to this state, instead of a blank cell
625 625 target_cell.code_mirror.clearHistory();
626 626 source_element.remove();
627 627 this.dirty = true;
628 628 };
629 629 };
630 630 };
631 631
632 632
633 633 Notebook.prototype.to_markdown = function (index) {
634 634 var i = this.index_or_selected(index);
635 635 if (this.is_valid_cell_index(i)) {
636 636 var source_element = this.get_cell_element(i);
637 637 var source_cell = source_element.data("cell");
638 638 if (!(source_cell instanceof IPython.MarkdownCell)) {
639 639 target_cell = this.insert_cell_below('markdown',i);
640 640 var text = source_cell.get_text();
641 641 if (text === source_cell.placeholder) {
642 642 text = '';
643 643 };
644 644 // The edit must come before the set_text.
645 645 target_cell.edit();
646 646 target_cell.set_text(text);
647 647 // make this value the starting point, so that we can only undo
648 648 // to this state, instead of a blank cell
649 649 target_cell.code_mirror.clearHistory();
650 650 source_element.remove();
651 651 this.dirty = true;
652 652 };
653 653 };
654 654 };
655 655
656 656
657 657 Notebook.prototype.to_html = function (index) {
658 658 var i = this.index_or_selected(index);
659 659 if (this.is_valid_cell_index(i)) {
660 660 var source_element = this.get_cell_element(i);
661 661 var source_cell = source_element.data("cell");
662 662 var target_cell = null;
663 663 if (!(source_cell instanceof IPython.HTMLCell)) {
664 664 target_cell = this.insert_cell_below('html',i);
665 665 var text = source_cell.get_text();
666 666 if (text === source_cell.placeholder) {
667 667 text = '';
668 668 };
669 669 // The edit must come before the set_text.
670 670 target_cell.edit();
671 671 target_cell.set_text(text);
672 672 // make this value the starting point, so that we can only undo
673 673 // to this state, instead of a blank cell
674 674 target_cell.code_mirror.clearHistory();
675 675 source_element.remove();
676 676 this.dirty = true;
677 677 };
678 678 };
679 679 };
680 680
681 681
682 682 Notebook.prototype.to_raw = function (index) {
683 683 var i = this.index_or_selected(index);
684 684 if (this.is_valid_cell_index(i)) {
685 685 var source_element = this.get_cell_element(i);
686 686 var source_cell = source_element.data("cell");
687 687 var target_cell = null;
688 688 if (!(source_cell instanceof IPython.RawCell)) {
689 689 target_cell = this.insert_cell_below('raw',i);
690 690 var text = source_cell.get_text();
691 691 if (text === source_cell.placeholder) {
692 692 text = '';
693 693 };
694 694 // The edit must come before the set_text.
695 695 target_cell.edit();
696 696 target_cell.set_text(text);
697 697 // make this value the starting point, so that we can only undo
698 698 // to this state, instead of a blank cell
699 699 target_cell.code_mirror.clearHistory();
700 700 source_element.remove();
701 701 this.dirty = true;
702 702 };
703 703 };
704 704 };
705 705
706 706
707 707 Notebook.prototype.to_heading = function (index, level) {
708 708 level = level || 1;
709 709 var i = this.index_or_selected(index);
710 710 if (this.is_valid_cell_index(i)) {
711 711 var source_element = this.get_cell_element(i);
712 712 var source_cell = source_element.data("cell");
713 713 var target_cell = null;
714 714 if (source_cell instanceof IPython.HeadingCell) {
715 715 source_cell.set_level(level);
716 716 } else {
717 717 target_cell = this.insert_cell_below('heading',i);
718 718 var text = source_cell.get_text();
719 719 if (text === source_cell.placeholder) {
720 720 text = '';
721 721 };
722 722 // The edit must come before the set_text.
723 723 target_cell.set_level(level);
724 724 target_cell.edit();
725 725 target_cell.set_text(text);
726 726 // make this value the starting point, so that we can only undo
727 727 // to this state, instead of a blank cell
728 728 target_cell.code_mirror.clearHistory();
729 729 source_element.remove();
730 730 this.dirty = true;
731 731 };
732 732 $([IPython.events]).trigger('selected_cell_type_changed.Notebook',
733 733 {'cell_type':'heading',level:level}
734 734 );
735 735 };
736 736 };
737 737
738 738
739 739 // Cut/Copy/Paste
740 740
741 741 Notebook.prototype.enable_paste = function () {
742 742 var that = this;
743 743 if (!this.paste_enabled) {
744 744 $('#paste_cell').removeClass('ui-state-disabled')
745 745 .on('click', function () {that.paste_cell();});
746 746 $('#paste_cell_above').removeClass('ui-state-disabled')
747 747 .on('click', function () {that.paste_cell_above();});
748 748 $('#paste_cell_below').removeClass('ui-state-disabled')
749 749 .on('click', function () {that.paste_cell_below();});
750 750 this.paste_enabled = true;
751 751 };
752 752 };
753 753
754 754
755 755 Notebook.prototype.disable_paste = function () {
756 756 if (this.paste_enabled) {
757 757 $('#paste_cell').addClass('ui-state-disabled').off('click');
758 758 $('#paste_cell_above').addClass('ui-state-disabled').off('click');
759 759 $('#paste_cell_below').addClass('ui-state-disabled').off('click');
760 760 this.paste_enabled = false;
761 761 };
762 762 };
763 763
764 764
765 765 Notebook.prototype.cut_cell = function () {
766 766 this.copy_cell();
767 767 this.delete_cell();
768 768 }
769 769
770 770 Notebook.prototype.copy_cell = function () {
771 771 var cell = this.get_selected_cell();
772 772 this.clipboard = cell.toJSON();
773 773 this.enable_paste();
774 774 };
775 775
776 776
777 777 Notebook.prototype.paste_cell = function () {
778 778 if (this.clipboard !== null && this.paste_enabled) {
779 779 var cell_data = this.clipboard;
780 780 var new_cell = this.insert_cell_above(cell_data.cell_type);
781 781 new_cell.fromJSON(cell_data);
782 782 old_cell = this.get_next_cell(new_cell);
783 783 this.delete_cell(this.find_cell_index(old_cell));
784 784 this.select(this.find_cell_index(new_cell));
785 785 };
786 786 };
787 787
788 788
789 789 Notebook.prototype.paste_cell_above = function () {
790 790 if (this.clipboard !== null && this.paste_enabled) {
791 791 var cell_data = this.clipboard;
792 792 var new_cell = this.insert_cell_above(cell_data.cell_type);
793 793 new_cell.fromJSON(cell_data);
794 794 };
795 795 };
796 796
797 797
798 798 Notebook.prototype.paste_cell_below = function () {
799 799 if (this.clipboard !== null && this.paste_enabled) {
800 800 var cell_data = this.clipboard;
801 801 var new_cell = this.insert_cell_below(cell_data.cell_type);
802 802 new_cell.fromJSON(cell_data);
803 803 };
804 804 };
805 805
806 806
807 807 // Split/merge
808 808
809 809 Notebook.prototype.split_cell = function () {
810 810 // Todo: implement spliting for other cell types.
811 811 var cell = this.get_selected_cell();
812 812 if (cell.is_splittable()) {
813 813 texta = cell.get_pre_cursor();
814 814 textb = cell.get_post_cursor();
815 815 if (cell instanceof IPython.CodeCell) {
816 816 cell.set_text(texta);
817 817 var new_cell = this.insert_cell_below('code');
818 818 new_cell.set_text(textb);
819 819 } else if (cell instanceof IPython.MarkdownCell) {
820 820 cell.set_text(texta);
821 821 cell.render();
822 822 var new_cell = this.insert_cell_below('markdown');
823 823 new_cell.edit(); // editor must be visible to call set_text
824 824 new_cell.set_text(textb);
825 825 new_cell.render();
826 826 } else if (cell instanceof IPython.HTMLCell) {
827 827 cell.set_text(texta);
828 828 cell.render();
829 829 var new_cell = this.insert_cell_below('html');
830 830 new_cell.edit(); // editor must be visible to call set_text
831 831 new_cell.set_text(textb);
832 832 new_cell.render();
833 833 };
834 834 };
835 835 };
836 836
837 837
838 838 Notebook.prototype.merge_cell_above = function () {
839 839 var index = this.get_selected_index();
840 840 var cell = this.get_cell(index);
841 841 if (index > 0) {
842 842 upper_cell = this.get_cell(index-1);
843 843 upper_text = upper_cell.get_text();
844 844 text = cell.get_text();
845 845 if (cell instanceof IPython.CodeCell) {
846 846 cell.set_text(upper_text+'\n'+text);
847 847 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
848 848 cell.edit();
849 849 cell.set_text(upper_text+'\n'+text);
850 850 cell.render();
851 851 };
852 852 this.delete_cell(index-1);
853 853 this.select(this.find_cell_index(cell));
854 854 };
855 855 };
856 856
857 857
858 858 Notebook.prototype.merge_cell_below = function () {
859 859 var index = this.get_selected_index();
860 860 var cell = this.get_cell(index);
861 861 if (index < this.ncells()-1) {
862 862 lower_cell = this.get_cell(index+1);
863 863 lower_text = lower_cell.get_text();
864 864 text = cell.get_text();
865 865 if (cell instanceof IPython.CodeCell) {
866 866 cell.set_text(text+'\n'+lower_text);
867 867 } else if (cell instanceof IPython.MarkdownCell || cell instanceof IPython.HTMLCell) {
868 868 cell.edit();
869 869 cell.set_text(text+'\n'+lower_text);
870 870 cell.render();
871 871 };
872 872 this.delete_cell(index+1);
873 873 this.select(this.find_cell_index(cell));
874 874 };
875 875 };
876 876
877 877
878 878 // Cell collapsing and output clearing
879 879
880 880 Notebook.prototype.collapse = function (index) {
881 881 var i = this.index_or_selected(index);
882 882 this.get_cell(i).collapse();
883 883 this.dirty = true;
884 884 };
885 885
886 886
887 887 Notebook.prototype.expand = function (index) {
888 888 var i = this.index_or_selected(index);
889 889 this.get_cell(i).expand();
890 890 this.dirty = true;
891 891 };
892 892
893 893
894 894 Notebook.prototype.toggle_output = function (index) {
895 895 var i = this.index_or_selected(index);
896 896 this.get_cell(i).toggle_output();
897 897 this.dirty = true;
898 898 };
899 899
900 900
901 901 Notebook.prototype.toggle_output_scroll = function (index) {
902 902 var i = this.index_or_selected(index);
903 903 this.get_cell(i).toggle_output_scroll();
904 904 };
905 905
906 906
907 907 Notebook.prototype.collapse_all_output = function () {
908 908 var ncells = this.ncells();
909 909 var cells = this.get_cells();
910 910 for (var i=0; i<ncells; i++) {
911 911 if (cells[i] instanceof IPython.CodeCell) {
912 912 cells[i].output_area.collapse();
913 913 }
914 914 };
915 915 // this should not be set if the `collapse` key is removed from nbformat
916 916 this.dirty = true;
917 917 };
918 918
919 919
920 920 Notebook.prototype.scroll_all_output = function () {
921 921 var ncells = this.ncells();
922 922 var cells = this.get_cells();
923 923 for (var i=0; i<ncells; i++) {
924 924 if (cells[i] instanceof IPython.CodeCell) {
925 925 cells[i].output_area.expand();
926 926 cells[i].output_area.scroll_if_long(20);
927 927 }
928 928 };
929 929 // this should not be set if the `collapse` key is removed from nbformat
930 930 this.dirty = true;
931 931 };
932 932
933 933
934 934 Notebook.prototype.expand_all_output = function () {
935 935 var ncells = this.ncells();
936 936 var cells = this.get_cells();
937 937 for (var i=0; i<ncells; i++) {
938 938 if (cells[i] instanceof IPython.CodeCell) {
939 939 cells[i].output_area.expand();
940 940 cells[i].output_area.unscroll_area();
941 941 }
942 942 };
943 943 // this should not be set if the `collapse` key is removed from nbformat
944 944 this.dirty = true;
945 945 };
946 946
947 947
948 948 Notebook.prototype.clear_all_output = function () {
949 949 var ncells = this.ncells();
950 950 var cells = this.get_cells();
951 951 for (var i=0; i<ncells; i++) {
952 952 if (cells[i] instanceof IPython.CodeCell) {
953 953 cells[i].clear_output(true,true,true);
954 954 // Make all In[] prompts blank, as well
955 955 // TODO: make this configurable (via checkbox?)
956 956 cells[i].set_input_prompt();
957 957 }
958 958 };
959 959 this.dirty = true;
960 960 };
961 961
962 962
963 963 // Other cell functions: line numbers, ...
964 964
965 965 Notebook.prototype.cell_toggle_line_numbers = function() {
966 966 this.get_selected_cell().toggle_line_numbers();
967 967 };
968 968
969 969 // Kernel related things
970 970
971 971 Notebook.prototype.start_kernel = function () {
972 972 var base_url = $('body').data('baseKernelUrl') + "kernels";
973 973 this.kernel = new IPython.Kernel(base_url);
974 974 this.kernel.start(this.notebook_id);
975 975 // Now that the kernel has been created, tell the CodeCells about it.
976 976 var ncells = this.ncells();
977 977 for (var i=0; i<ncells; i++) {
978 978 var cell = this.get_cell(i);
979 979 if (cell instanceof IPython.CodeCell) {
980 980 cell.set_kernel(this.kernel)
981 981 };
982 982 };
983 983 };
984 984
985 985
986 986 Notebook.prototype.restart_kernel = function () {
987 987 var that = this;
988 988 var dialog = $('<div/>');
989 989 dialog.html('Do you want to restart the current kernel? You will lose all variables defined in it.');
990 990 $(document).append(dialog);
991 991 dialog.dialog({
992 992 resizable: false,
993 993 modal: true,
994 994 title: "Restart kernel or continue running?",
995 995 closeText: '',
996 996 buttons : {
997 997 "Restart": function () {
998 998 that.kernel.restart();
999 999 $(this).dialog('close');
1000 1000 },
1001 1001 "Continue running": function () {
1002 1002 $(this).dialog('close');
1003 1003 }
1004 1004 }
1005 1005 });
1006 1006 };
1007 1007
1008 1008
1009 1009 Notebook.prototype.execute_selected_cell = function (options) {
1010 1010 // add_new: should a new cell be added if we are at the end of the nb
1011 1011 // terminal: execute in terminal mode, which stays in the current cell
1012 1012 default_options = {terminal: false, add_new: true};
1013 1013 $.extend(default_options, options);
1014 1014 var that = this;
1015 1015 var cell = that.get_selected_cell();
1016 1016 var cell_index = that.find_cell_index(cell);
1017 1017 if (cell instanceof IPython.CodeCell) {
1018 1018 cell.execute();
1019 1019 } else if (cell instanceof IPython.HTMLCell) {
1020 1020 cell.render();
1021 1021 }
1022 1022 if (default_options.terminal) {
1023 1023 cell.select_all();
1024 1024 } else {
1025 1025 if ((cell_index === (that.ncells()-1)) && default_options.add_new) {
1026 1026 that.insert_cell_below('code');
1027 1027 // If we are adding a new cell at the end, scroll down to show it.
1028 1028 that.scroll_to_bottom();
1029 1029 } else {
1030 1030 that.select(cell_index+1);
1031 1031 };
1032 1032 };
1033 1033 this.dirty = true;
1034 1034 };
1035 1035
1036 1036
1037 1037 Notebook.prototype.execute_all_cells = function () {
1038 1038 var ncells = this.ncells();
1039 1039 for (var i=0; i<ncells; i++) {
1040 1040 this.select(i);
1041 1041 this.execute_selected_cell({add_new:false});
1042 1042 };
1043 1043 this.scroll_to_bottom();
1044 1044 };
1045 1045
1046 1046 // Persistance and loading
1047 1047
1048 1048 Notebook.prototype.get_notebook_id = function () {
1049 1049 return this.notebook_id;
1050 1050 };
1051 1051
1052 1052
1053 1053 Notebook.prototype.get_notebook_name = function () {
1054 1054 return this.notebook_name;
1055 1055 };
1056 1056
1057 1057
1058 1058 Notebook.prototype.set_notebook_name = function (name) {
1059 1059 this.notebook_name = name;
1060 1060 };
1061 1061
1062 1062
1063 1063 Notebook.prototype.test_notebook_name = function (nbname) {
1064 1064 nbname = nbname || '';
1065 1065 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1066 1066 return true;
1067 1067 } else {
1068 1068 return false;
1069 1069 };
1070 1070 };
1071 1071
1072 1072
1073 1073 Notebook.prototype.fromJSON = function (data) {
1074 1074 var ncells = this.ncells();
1075 1075 var i;
1076 1076 for (i=0; i<ncells; i++) {
1077 1077 // Always delete cell 0 as they get renumbered as they are deleted.
1078 1078 this.delete_cell(0);
1079 1079 };
1080 1080 // Save the metadata and name.
1081 1081 this.metadata = data.metadata;
1082 1082 this.notebook_name = data.metadata.name;
1083 1083 // Only handle 1 worksheet for now.
1084 1084 var worksheet = data.worksheets[0];
1085 1085 if (worksheet !== undefined) {
1086 1086 if (worksheet.metadata) {
1087 1087 this.worksheet_metadata = worksheet.metadata;
1088 1088 }
1089 1089 var new_cells = worksheet.cells;
1090 1090 ncells = new_cells.length;
1091 1091 var cell_data = null;
1092 1092 var new_cell = null;
1093 1093 for (i=0; i<ncells; i++) {
1094 1094 cell_data = new_cells[i];
1095 1095 // VERSIONHACK: plaintext -> raw
1096 1096 // handle never-released plaintext name for raw cells
1097 1097 if (cell_data.cell_type === 'plaintext'){
1098 1098 cell_data.cell_type = 'raw';
1099 1099 }
1100 1100
1101 1101 new_cell = this.insert_cell_below(cell_data.cell_type);
1102 1102 new_cell.fromJSON(cell_data);
1103 1103 };
1104 1104 };
1105 1105 if (data.worksheets.length > 1) {
1106 1106 var dialog = $('<div/>');
1107 1107 dialog.html("This notebook has " + data.worksheets.length + " worksheets, " +
1108 1108 "but this version of IPython can only handle the first. " +
1109 1109 "If you save this notebook, worksheets after the first will be lost."
1110 1110 );
1111 1111 this.element.append(dialog);
1112 1112 dialog.dialog({
1113 1113 resizable: false,
1114 1114 modal: true,
1115 1115 title: "Multiple worksheets",
1116 1116 closeText: "",
1117 1117 close: function(event, ui) {$(this).dialog('destroy').remove();},
1118 1118 buttons : {
1119 1119 "OK": function () {
1120 1120 $(this).dialog('close');
1121 1121 }
1122 1122 },
1123 1123 width: 400
1124 1124 });
1125 1125 }
1126 1126 };
1127 1127
1128 1128
1129 1129 Notebook.prototype.toJSON = function () {
1130 1130 var cells = this.get_cells();
1131 1131 var ncells = cells.length;
1132 1132 var cell_array = new Array(ncells);
1133 1133 for (var i=0; i<ncells; i++) {
1134 1134 cell_array[i] = cells[i].toJSON();
1135 1135 };
1136 1136 var data = {
1137 1137 // Only handle 1 worksheet for now.
1138 1138 worksheets : [{
1139 1139 cells: cell_array,
1140 1140 metadata: this.worksheet_metadata
1141 1141 }],
1142 1142 metadata : this.metadata
1143 1143 };
1144 1144 return data;
1145 1145 };
1146 1146
1147 1147 Notebook.prototype.save_notebook = function () {
1148 1148 // We may want to move the name/id/nbformat logic inside toJSON?
1149 1149 var data = this.toJSON();
1150 1150 data.metadata.name = this.notebook_name;
1151 1151 data.nbformat = this.nbformat;
1152 1152 data.nbformat_minor = this.nbformat_minor;
1153 1153 // We do the call with settings so we can set cache to false.
1154 1154 var settings = {
1155 1155 processData : false,
1156 1156 cache : false,
1157 1157 type : "PUT",
1158 1158 data : JSON.stringify(data),
1159 1159 headers : {'Content-Type': 'application/json'},
1160 1160 success : $.proxy(this.save_notebook_success,this),
1161 1161 error : $.proxy(this.save_notebook_error,this)
1162 1162 };
1163 1163 $([IPython.events]).trigger('notebook_saving.Notebook');
1164 1164 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1165 1165 $.ajax(url, settings);
1166 1166 };
1167 1167
1168 1168
1169 1169 Notebook.prototype.save_notebook_success = function (data, status, xhr) {
1170 1170 this.dirty = false;
1171 1171 $([IPython.events]).trigger('notebook_saved.Notebook');
1172 1172 };
1173 1173
1174 1174
1175 1175 Notebook.prototype.save_notebook_error = function (xhr, status, error_msg) {
1176 1176 $([IPython.events]).trigger('notebook_save_failed.Notebook');
1177 1177 };
1178 1178
1179 1179
1180 1180 Notebook.prototype.load_notebook = function (notebook_id) {
1181 1181 var that = this;
1182 1182 this.notebook_id = notebook_id;
1183 1183 // We do the call with settings so we can set cache to false.
1184 1184 var settings = {
1185 1185 processData : false,
1186 1186 cache : false,
1187 1187 type : "GET",
1188 1188 dataType : "json",
1189 1189 success : $.proxy(this.load_notebook_success,this),
1190 1190 error : $.proxy(this.load_notebook_error,this),
1191 1191 };
1192 1192 $([IPython.events]).trigger('notebook_loading.Notebook');
1193 1193 var url = $('body').data('baseProjectUrl') + 'notebooks/' + this.notebook_id;
1194 1194 $.ajax(url, settings);
1195 1195 };
1196 1196
1197 1197
1198 1198 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1199 1199 this.fromJSON(data);
1200 1200 if (this.ncells() === 0) {
1201 1201 this.insert_cell_below('code');
1202 1202 };
1203 1203 this.dirty = false;
1204 1204 this.select(0);
1205 1205 this.scroll_to_top();
1206 1206 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1207 1207 msg = "This notebook has been converted from an older " +
1208 1208 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1209 1209 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1210 1210 "newer notebook format will be used and older verions of IPython " +
1211 1211 "may not be able to read it. To keep the older version, close the " +
1212 1212 "notebook without saving it.";
1213 1213 var dialog = $('<div/>');
1214 1214 dialog.html(msg);
1215 1215 this.element.append(dialog);
1216 1216 dialog.dialog({
1217 1217 resizable: false,
1218 1218 modal: true,
1219 1219 title: "Notebook converted",
1220 1220 closeText: "",
1221 1221 close: function(event, ui) {$(this).dialog('destroy').remove();},
1222 1222 buttons : {
1223 1223 "OK": function () {
1224 1224 $(this).dialog('close');
1225 1225 }
1226 1226 },
1227 1227 width: 400
1228 1228 });
1229 1229 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1230 1230 var that = this;
1231 1231 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1232 1232 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1233 1233 msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1234 1234 this_vs + ". You can still work with this notebook, but some features " +
1235 1235 "introduced in later notebook versions may not be available."
1236 1236
1237 1237 var dialog = $('<div/>');
1238 1238 dialog.html(msg);
1239 1239 this.element.append(dialog);
1240 1240 dialog.dialog({
1241 1241 resizable: false,
1242 1242 modal: true,
1243 1243 title: "Newer Notebook",
1244 1244 closeText: "",
1245 1245 close: function(event, ui) {$(this).dialog('destroy').remove();},
1246 1246 buttons : {
1247 1247 "OK": function () {
1248 1248 $(this).dialog('close');
1249 1249 }
1250 1250 },
1251 1251 width: 400
1252 1252 });
1253 1253
1254 1254 }
1255 1255 // Create the kernel after the notebook is completely loaded to prevent
1256 1256 // code execution upon loading, which is a security risk.
1257 1257 if (! this.read_only) {
1258 1258 this.start_kernel();
1259 1259 }
1260 1260 $([IPython.events]).trigger('notebook_loaded.Notebook');
1261 1261 };
1262 1262
1263 1263
1264 1264 Notebook.prototype.load_notebook_error = function (xhr, textStatus, errorThrow) {
1265 1265 if (xhr.status === 500) {
1266 1266 msg = "An error occurred while loading this notebook. Most likely " +
1267 1267 "this notebook is in a newer format than is supported by this " +
1268 1268 "version of IPython. This version can load notebook formats " +
1269 1269 "v"+this.nbformat+" or earlier.";
1270 1270 var dialog = $('<div/>');
1271 1271 dialog.html(msg);
1272 1272 this.element.append(dialog);
1273 1273 dialog.dialog({
1274 1274 resizable: false,
1275 1275 modal: true,
1276 1276 title: "Error loading notebook",
1277 1277 closeText: "",
1278 1278 close: function(event, ui) {$(this).dialog('destroy').remove();},
1279 1279 buttons : {
1280 1280 "OK": function () {
1281 1281 $(this).dialog('close');
1282 1282 }
1283 1283 },
1284 1284 width: 400
1285 1285 });
1286 1286 }
1287 1287 }
1288 1288
1289 1289 IPython.Notebook = Notebook;
1290 1290
1291 1291
1292 1292 return IPython;
1293 1293
1294 1294 }(IPython));
1295 1295
General Comments 0
You need to be logged in to leave comments. Login now