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