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