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