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