##// END OF EJS Templates
Converting loops in *_all_output to $.map().
Brian E. Granger -
Show More
@@ -1,2263 +1,2255 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 var ncells = this.ncells();
1186 $.map(this.get_cells(), function (cell, i) {
1187 var cells = this.get_cells();
1187 if (cell instanceof IPython.CodeCell) {
1188 for (var i=0; i<ncells; i++) {
1188 cell.collapse_output();
1189 if (cells[i] instanceof IPython.CodeCell) {
1190 cells[i].collapse_output();
1191 }
1189 }
1192 };
1190 });
1193 // 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
1194 this.set_dirty(true);
1192 this.set_dirty(true);
1195 };
1193 };
1196
1194
1197 /**
1195 /**
1198 * Show a cell's output.
1196 * Show a cell's output.
1199 *
1197 *
1200 * @method expand_output
1198 * @method expand_output
1201 * @param {Number} index A cell's numeric index
1199 * @param {Number} index A cell's numeric index
1202 */
1200 */
1203 Notebook.prototype.expand_output = function (index) {
1201 Notebook.prototype.expand_output = function (index) {
1204 var i = this.index_or_selected(index);
1202 var i = this.index_or_selected(index);
1205 var cell = this.get_cell(i);
1203 var cell = this.get_cell(i);
1206 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1204 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1207 cell.expand_output();
1205 cell.expand_output();
1208 this.set_dirty(true);
1206 this.set_dirty(true);
1209 }
1207 }
1210 };
1208 };
1211
1209
1212 /**
1210 /**
1213 * Expand each code cell's output area, and remove scrollbars.
1211 * Expand each code cell's output area, and remove scrollbars.
1214 *
1212 *
1215 * @method expand_all_output
1213 * @method expand_all_output
1216 */
1214 */
1217 Notebook.prototype.expand_all_output = function () {
1215 Notebook.prototype.expand_all_output = function () {
1218 var ncells = this.ncells();
1216 $.map(this.get_cells(), function (cell, i) {
1219 var cells = this.get_cells();
1217 if (cell instanceof IPython.CodeCell) {
1220 for (var i=0; i<ncells; i++) {
1218 cell.expand_output();
1221 if (cells[i] instanceof IPython.CodeCell) {
1222 cells[i].expand_output();
1223 }
1219 }
1224 };
1220 });
1225 // 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
1226 this.set_dirty(true);
1222 this.set_dirty(true);
1227 };
1223 };
1228
1224
1229 /**
1225 /**
1230 * Clear the selected CodeCell's output area.
1226 * Clear the selected CodeCell's output area.
1231 *
1227 *
1232 * @method clear_output
1228 * @method clear_output
1233 * @param {Number} index A cell's numeric index
1229 * @param {Number} index A cell's numeric index
1234 */
1230 */
1235 Notebook.prototype.clear_output = function (index) {
1231 Notebook.prototype.clear_output = function (index) {
1236 var i = this.index_or_selected(index);
1232 var i = this.index_or_selected(index);
1237 var cell = this.get_cell(i);
1233 var cell = this.get_cell(i);
1238 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1234 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1239 cell.clear_output();
1235 cell.clear_output();
1240 this.set_dirty(true);
1236 this.set_dirty(true);
1241 }
1237 }
1242 };
1238 };
1243
1239
1244 /**
1240 /**
1245 * Clear each code cell's output area.
1241 * Clear each code cell's output area.
1246 *
1242 *
1247 * @method clear_all_output
1243 * @method clear_all_output
1248 */
1244 */
1249 Notebook.prototype.clear_all_output = function () {
1245 Notebook.prototype.clear_all_output = function () {
1250 var ncells = this.ncells();
1246 $.map(this.get_cells(), function (cell, i) {
1251 var cells = this.get_cells();
1247 if (cell instanceof IPython.CodeCell) {
1252 for (var i=0; i<ncells; i++) {
1248 cell.clear_output();
1253 if (cells[i] instanceof IPython.CodeCell) {
1254 cells[i].clear_output();
1255 }
1249 }
1256 };
1250 });
1257 this.set_dirty(true);
1251 this.set_dirty(true);
1258 };
1252 };
1259
1253
1260 /**
1254 /**
1261 * Scroll the selected CodeCell's output area.
1255 * Scroll the selected CodeCell's output area.
1262 *
1256 *
1263 * @method scroll_output
1257 * @method scroll_output
1264 * @param {Number} index A cell's numeric index
1258 * @param {Number} index A cell's numeric index
1265 */
1259 */
1266 Notebook.prototype.scroll_output = function (index) {
1260 Notebook.prototype.scroll_output = function (index) {
1267 var i = this.index_or_selected(index);
1261 var i = this.index_or_selected(index);
1268 var cell = this.get_cell(i);
1262 var cell = this.get_cell(i);
1269 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1263 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1270 cell.scroll_output();
1264 cell.scroll_output();
1271 this.set_dirty(true);
1265 this.set_dirty(true);
1272 }
1266 }
1273 };
1267 };
1274
1268
1275 /**
1269 /**
1276 * 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.
1277 *
1271 *
1278 * @method scroll_all_output
1272 * @method scroll_all_output
1279 */
1273 */
1280 Notebook.prototype.scroll_all_output = function () {
1274 Notebook.prototype.scroll_all_output = function () {
1281 var ncells = this.ncells();
1275 $.map(this.get_cells(), function (cell, i) {
1282 var cells = this.get_cells();
1276 if (cell instanceof IPython.CodeCell) {
1283 for (var i=0; i<ncells; i++) {
1277 cell.scroll_output();
1284 if (cells[i] instanceof IPython.CodeCell) {
1285 cells[i].scroll_output();
1286 }
1278 }
1287 };
1279 });
1288 // 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
1289 this.set_dirty(true);
1281 this.set_dirty(true);
1290 };
1282 };
1291
1283
1292 /** Toggle whether a cell's output is collapsed or expanded.
1284 /** Toggle whether a cell's output is collapsed or expanded.
1293 *
1285 *
1294 * @method toggle_output
1286 * @method toggle_output
1295 * @param {Number} index A cell's numeric index
1287 * @param {Number} index A cell's numeric index
1296 */
1288 */
1297 Notebook.prototype.toggle_output = function (index) {
1289 Notebook.prototype.toggle_output = function (index) {
1298 var i = this.index_or_selected(index);
1290 var i = this.index_or_selected(index);
1299 var cell = this.get_cell(i);
1291 var cell = this.get_cell(i);
1300 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1292 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1301 cell.toggle_output();
1293 cell.toggle_output();
1302 this.set_dirty(true);
1294 this.set_dirty(true);
1303 }
1295 }
1304 };
1296 };
1305
1297
1306 /**
1298 /**
1307 * Toggle a scrollbar for long cell outputs.
1299 * Toggle a scrollbar for long cell outputs.
1308 *
1300 *
1309 * @method toggle_output_scroll
1301 * @method toggle_output_scroll
1310 * @param {Number} index A cell's numeric index
1302 * @param {Number} index A cell's numeric index
1311 */
1303 */
1312 Notebook.prototype.toggle_output_scroll = function (index) {
1304 Notebook.prototype.toggle_output_scroll = function (index) {
1313 var i = this.index_or_selected(index);
1305 var i = this.index_or_selected(index);
1314 var cell = this.get_cell(i);
1306 var cell = this.get_cell(i);
1315 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1307 if (cell !== null && (cell instanceof IPython.CodeCell)) {
1316 cell.toggle_output_scroll();
1308 cell.toggle_output_scroll();
1317 this.set_dirty(true);
1309 this.set_dirty(true);
1318 }
1310 }
1319 };
1311 };
1320
1312
1321
1313
1322 // Other cell functions: line numbers, ...
1314 // Other cell functions: line numbers, ...
1323
1315
1324 /**
1316 /**
1325 * Toggle line numbers in the selected cell's input area.
1317 * Toggle line numbers in the selected cell's input area.
1326 *
1318 *
1327 * @method cell_toggle_line_numbers
1319 * @method cell_toggle_line_numbers
1328 */
1320 */
1329 Notebook.prototype.cell_toggle_line_numbers = function() {
1321 Notebook.prototype.cell_toggle_line_numbers = function() {
1330 this.get_selected_cell().toggle_line_numbers();
1322 this.get_selected_cell().toggle_line_numbers();
1331 };
1323 };
1332
1324
1333 // Session related things
1325 // Session related things
1334
1326
1335 /**
1327 /**
1336 * Start a new session and set it on each code cell.
1328 * Start a new session and set it on each code cell.
1337 *
1329 *
1338 * @method start_session
1330 * @method start_session
1339 */
1331 */
1340 Notebook.prototype.start_session = function () {
1332 Notebook.prototype.start_session = function () {
1341 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1333 this.session = new IPython.Session(this.notebook_name, this.notebook_path, this);
1342 this.session.start($.proxy(this._session_started, this));
1334 this.session.start($.proxy(this._session_started, this));
1343 };
1335 };
1344
1336
1345
1337
1346 /**
1338 /**
1347 * Once a session is started, link the code cells to the kernel and pass the
1339 * Once a session is started, link the code cells to the kernel and pass the
1348 * comm manager to the widget manager
1340 * comm manager to the widget manager
1349 *
1341 *
1350 */
1342 */
1351 Notebook.prototype._session_started = function(){
1343 Notebook.prototype._session_started = function(){
1352 this.kernel = this.session.kernel;
1344 this.kernel = this.session.kernel;
1353 var ncells = this.ncells();
1345 var ncells = this.ncells();
1354 for (var i=0; i<ncells; i++) {
1346 for (var i=0; i<ncells; i++) {
1355 var cell = this.get_cell(i);
1347 var cell = this.get_cell(i);
1356 if (cell instanceof IPython.CodeCell) {
1348 if (cell instanceof IPython.CodeCell) {
1357 cell.set_kernel(this.session.kernel);
1349 cell.set_kernel(this.session.kernel);
1358 };
1350 };
1359 };
1351 };
1360 };
1352 };
1361
1353
1362 /**
1354 /**
1363 * Prompt the user to restart the IPython kernel.
1355 * Prompt the user to restart the IPython kernel.
1364 *
1356 *
1365 * @method restart_kernel
1357 * @method restart_kernel
1366 */
1358 */
1367 Notebook.prototype.restart_kernel = function () {
1359 Notebook.prototype.restart_kernel = function () {
1368 var that = this;
1360 var that = this;
1369 IPython.dialog.modal({
1361 IPython.dialog.modal({
1370 title : "Restart kernel or continue running?",
1362 title : "Restart kernel or continue running?",
1371 body : $("<p/>").text(
1363 body : $("<p/>").text(
1372 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1364 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1373 ),
1365 ),
1374 buttons : {
1366 buttons : {
1375 "Continue running" : {},
1367 "Continue running" : {},
1376 "Restart" : {
1368 "Restart" : {
1377 "class" : "btn-danger",
1369 "class" : "btn-danger",
1378 "click" : function() {
1370 "click" : function() {
1379 that.session.restart_kernel();
1371 that.session.restart_kernel();
1380 }
1372 }
1381 }
1373 }
1382 }
1374 }
1383 });
1375 });
1384 };
1376 };
1385
1377
1386 /**
1378 /**
1387 * Execute or render cell outputs and go into command mode.
1379 * Execute or render cell outputs and go into command mode.
1388 *
1380 *
1389 * @method execute_cell
1381 * @method execute_cell
1390 */
1382 */
1391 Notebook.prototype.execute_cell = function () {
1383 Notebook.prototype.execute_cell = function () {
1392 // mode = shift, ctrl, alt
1384 // mode = shift, ctrl, alt
1393 var cell = this.get_selected_cell();
1385 var cell = this.get_selected_cell();
1394 var cell_index = this.find_cell_index(cell);
1386 var cell_index = this.find_cell_index(cell);
1395
1387
1396 cell.execute();
1388 cell.execute();
1397 this.command_mode();
1389 this.command_mode();
1398 cell.focus_cell();
1390 cell.focus_cell();
1399 this.set_dirty(true);
1391 this.set_dirty(true);
1400 }
1392 }
1401
1393
1402 /**
1394 /**
1403 * Execute or render cell outputs and insert a new cell below.
1395 * Execute or render cell outputs and insert a new cell below.
1404 *
1396 *
1405 * @method execute_cell_and_insert_below
1397 * @method execute_cell_and_insert_below
1406 */
1398 */
1407 Notebook.prototype.execute_cell_and_insert_below = function () {
1399 Notebook.prototype.execute_cell_and_insert_below = function () {
1408 var cell = this.get_selected_cell();
1400 var cell = this.get_selected_cell();
1409 var cell_index = this.find_cell_index(cell);
1401 var cell_index = this.find_cell_index(cell);
1410
1402
1411 cell.execute();
1403 cell.execute();
1412
1404
1413 // If we are at the end always insert a new cell and return
1405 // If we are at the end always insert a new cell and return
1414 if (cell_index === (this.ncells()-1)) {
1406 if (cell_index === (this.ncells()-1)) {
1415 this.insert_cell_below('code');
1407 this.insert_cell_below('code');
1416 this.select(cell_index+1);
1408 this.select(cell_index+1);
1417 this.edit_mode();
1409 this.edit_mode();
1418 this.scroll_to_bottom();
1410 this.scroll_to_bottom();
1419 this.set_dirty(true);
1411 this.set_dirty(true);
1420 return;
1412 return;
1421 }
1413 }
1422
1414
1423 // Only insert a new cell, if we ended up in an already populated cell
1415 // Only insert a new cell, if we ended up in an already populated cell
1424 var next_text = this.get_cell(cell_index+1).get_text();
1416 var next_text = this.get_cell(cell_index+1).get_text();
1425 if (/\S/.test(next_text) === true) {
1417 if (/\S/.test(next_text) === true) {
1426 this.insert_cell_below('code');
1418 this.insert_cell_below('code');
1427 }
1419 }
1428 this.select(cell_index+1);
1420 this.select(cell_index+1);
1429 this.edit_mode();
1421 this.edit_mode();
1430 this.set_dirty(true);
1422 this.set_dirty(true);
1431 };
1423 };
1432
1424
1433 /**
1425 /**
1434 * Execute or render cell outputs and select the next cell.
1426 * Execute or render cell outputs and select the next cell.
1435 *
1427 *
1436 * @method execute_cell_and_select_below
1428 * @method execute_cell_and_select_below
1437 */
1429 */
1438 Notebook.prototype.execute_cell_and_select_below = function () {
1430 Notebook.prototype.execute_cell_and_select_below = function () {
1439
1431
1440 var cell = this.get_selected_cell();
1432 var cell = this.get_selected_cell();
1441 var cell_index = this.find_cell_index(cell);
1433 var cell_index = this.find_cell_index(cell);
1442
1434
1443 cell.execute();
1435 cell.execute();
1444
1436
1445 // If we are at the end always insert a new cell and return
1437 // If we are at the end always insert a new cell and return
1446 if (cell_index === (this.ncells()-1)) {
1438 if (cell_index === (this.ncells()-1)) {
1447 this.insert_cell_below('code');
1439 this.insert_cell_below('code');
1448 this.select(cell_index+1);
1440 this.select(cell_index+1);
1449 this.edit_mode();
1441 this.edit_mode();
1450 this.scroll_to_bottom();
1442 this.scroll_to_bottom();
1451 this.set_dirty(true);
1443 this.set_dirty(true);
1452 return;
1444 return;
1453 }
1445 }
1454
1446
1455 this.select(cell_index+1);
1447 this.select(cell_index+1);
1456 this.get_cell(cell_index+1).focus_cell();
1448 this.get_cell(cell_index+1).focus_cell();
1457 this.set_dirty(true);
1449 this.set_dirty(true);
1458 };
1450 };
1459
1451
1460 /**
1452 /**
1461 * Execute all cells below the selected cell.
1453 * Execute all cells below the selected cell.
1462 *
1454 *
1463 * @method execute_cells_below
1455 * @method execute_cells_below
1464 */
1456 */
1465 Notebook.prototype.execute_cells_below = function () {
1457 Notebook.prototype.execute_cells_below = function () {
1466 this.execute_cell_range(this.get_selected_index(), this.ncells());
1458 this.execute_cell_range(this.get_selected_index(), this.ncells());
1467 this.scroll_to_bottom();
1459 this.scroll_to_bottom();
1468 };
1460 };
1469
1461
1470 /**
1462 /**
1471 * Execute all cells above the selected cell.
1463 * Execute all cells above the selected cell.
1472 *
1464 *
1473 * @method execute_cells_above
1465 * @method execute_cells_above
1474 */
1466 */
1475 Notebook.prototype.execute_cells_above = function () {
1467 Notebook.prototype.execute_cells_above = function () {
1476 this.execute_cell_range(0, this.get_selected_index());
1468 this.execute_cell_range(0, this.get_selected_index());
1477 };
1469 };
1478
1470
1479 /**
1471 /**
1480 * Execute all cells.
1472 * Execute all cells.
1481 *
1473 *
1482 * @method execute_all_cells
1474 * @method execute_all_cells
1483 */
1475 */
1484 Notebook.prototype.execute_all_cells = function () {
1476 Notebook.prototype.execute_all_cells = function () {
1485 this.execute_cell_range(0, this.ncells());
1477 this.execute_cell_range(0, this.ncells());
1486 this.scroll_to_bottom();
1478 this.scroll_to_bottom();
1487 };
1479 };
1488
1480
1489 /**
1481 /**
1490 * Execute a contiguous range of cells.
1482 * Execute a contiguous range of cells.
1491 *
1483 *
1492 * @method execute_cell_range
1484 * @method execute_cell_range
1493 * @param {Number} start Index of the first cell to execute (inclusive)
1485 * @param {Number} start Index of the first cell to execute (inclusive)
1494 * @param {Number} end Index of the last cell to execute (exclusive)
1486 * @param {Number} end Index of the last cell to execute (exclusive)
1495 */
1487 */
1496 Notebook.prototype.execute_cell_range = function (start, end) {
1488 Notebook.prototype.execute_cell_range = function (start, end) {
1497 for (var i=start; i<end; i++) {
1489 for (var i=start; i<end; i++) {
1498 this.select(i);
1490 this.select(i);
1499 this.execute_cell();
1491 this.execute_cell();
1500 };
1492 };
1501 };
1493 };
1502
1494
1503 // Persistance and loading
1495 // Persistance and loading
1504
1496
1505 /**
1497 /**
1506 * Getter method for this notebook's name.
1498 * Getter method for this notebook's name.
1507 *
1499 *
1508 * @method get_notebook_name
1500 * @method get_notebook_name
1509 * @return {String} This notebook's name
1501 * @return {String} This notebook's name
1510 */
1502 */
1511 Notebook.prototype.get_notebook_name = function () {
1503 Notebook.prototype.get_notebook_name = function () {
1512 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1504 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1513 return nbname;
1505 return nbname;
1514 };
1506 };
1515
1507
1516 /**
1508 /**
1517 * Setter method for this notebook's name.
1509 * Setter method for this notebook's name.
1518 *
1510 *
1519 * @method set_notebook_name
1511 * @method set_notebook_name
1520 * @param {String} name A new name for this notebook
1512 * @param {String} name A new name for this notebook
1521 */
1513 */
1522 Notebook.prototype.set_notebook_name = function (name) {
1514 Notebook.prototype.set_notebook_name = function (name) {
1523 this.notebook_name = name;
1515 this.notebook_name = name;
1524 };
1516 };
1525
1517
1526 /**
1518 /**
1527 * Check that a notebook's name is valid.
1519 * Check that a notebook's name is valid.
1528 *
1520 *
1529 * @method test_notebook_name
1521 * @method test_notebook_name
1530 * @param {String} nbname A name for this notebook
1522 * @param {String} nbname A name for this notebook
1531 * @return {Boolean} True if the name is valid, false if invalid
1523 * @return {Boolean} True if the name is valid, false if invalid
1532 */
1524 */
1533 Notebook.prototype.test_notebook_name = function (nbname) {
1525 Notebook.prototype.test_notebook_name = function (nbname) {
1534 nbname = nbname || '';
1526 nbname = nbname || '';
1535 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1527 if (this.notebook_name_blacklist_re.test(nbname) == false && nbname.length>0) {
1536 return true;
1528 return true;
1537 } else {
1529 } else {
1538 return false;
1530 return false;
1539 };
1531 };
1540 };
1532 };
1541
1533
1542 /**
1534 /**
1543 * Load a notebook from JSON (.ipynb).
1535 * Load a notebook from JSON (.ipynb).
1544 *
1536 *
1545 * This currently handles one worksheet: others are deleted.
1537 * This currently handles one worksheet: others are deleted.
1546 *
1538 *
1547 * @method fromJSON
1539 * @method fromJSON
1548 * @param {Object} data JSON representation of a notebook
1540 * @param {Object} data JSON representation of a notebook
1549 */
1541 */
1550 Notebook.prototype.fromJSON = function (data) {
1542 Notebook.prototype.fromJSON = function (data) {
1551 var content = data.content;
1543 var content = data.content;
1552 var ncells = this.ncells();
1544 var ncells = this.ncells();
1553 var i;
1545 var i;
1554 for (i=0; i<ncells; i++) {
1546 for (i=0; i<ncells; i++) {
1555 // Always delete cell 0 as they get renumbered as they are deleted.
1547 // Always delete cell 0 as they get renumbered as they are deleted.
1556 this.delete_cell(0);
1548 this.delete_cell(0);
1557 };
1549 };
1558 // Save the metadata and name.
1550 // Save the metadata and name.
1559 this.metadata = content.metadata;
1551 this.metadata = content.metadata;
1560 this.notebook_name = data.name;
1552 this.notebook_name = data.name;
1561 // Only handle 1 worksheet for now.
1553 // Only handle 1 worksheet for now.
1562 var worksheet = content.worksheets[0];
1554 var worksheet = content.worksheets[0];
1563 if (worksheet !== undefined) {
1555 if (worksheet !== undefined) {
1564 if (worksheet.metadata) {
1556 if (worksheet.metadata) {
1565 this.worksheet_metadata = worksheet.metadata;
1557 this.worksheet_metadata = worksheet.metadata;
1566 }
1558 }
1567 var new_cells = worksheet.cells;
1559 var new_cells = worksheet.cells;
1568 ncells = new_cells.length;
1560 ncells = new_cells.length;
1569 var cell_data = null;
1561 var cell_data = null;
1570 var new_cell = null;
1562 var new_cell = null;
1571 for (i=0; i<ncells; i++) {
1563 for (i=0; i<ncells; i++) {
1572 cell_data = new_cells[i];
1564 cell_data = new_cells[i];
1573 // VERSIONHACK: plaintext -> raw
1565 // VERSIONHACK: plaintext -> raw
1574 // handle never-released plaintext name for raw cells
1566 // handle never-released plaintext name for raw cells
1575 if (cell_data.cell_type === 'plaintext'){
1567 if (cell_data.cell_type === 'plaintext'){
1576 cell_data.cell_type = 'raw';
1568 cell_data.cell_type = 'raw';
1577 }
1569 }
1578
1570
1579 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1571 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1580 new_cell.fromJSON(cell_data);
1572 new_cell.fromJSON(cell_data);
1581 };
1573 };
1582 };
1574 };
1583 if (content.worksheets.length > 1) {
1575 if (content.worksheets.length > 1) {
1584 IPython.dialog.modal({
1576 IPython.dialog.modal({
1585 title : "Multiple worksheets",
1577 title : "Multiple worksheets",
1586 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1578 body : "This notebook has " + data.worksheets.length + " worksheets, " +
1587 "but this version of IPython can only handle the first. " +
1579 "but this version of IPython can only handle the first. " +
1588 "If you save this notebook, worksheets after the first will be lost.",
1580 "If you save this notebook, worksheets after the first will be lost.",
1589 buttons : {
1581 buttons : {
1590 OK : {
1582 OK : {
1591 class : "btn-danger"
1583 class : "btn-danger"
1592 }
1584 }
1593 }
1585 }
1594 });
1586 });
1595 }
1587 }
1596 };
1588 };
1597
1589
1598 /**
1590 /**
1599 * Dump this notebook into a JSON-friendly object.
1591 * Dump this notebook into a JSON-friendly object.
1600 *
1592 *
1601 * @method toJSON
1593 * @method toJSON
1602 * @return {Object} A JSON-friendly representation of this notebook.
1594 * @return {Object} A JSON-friendly representation of this notebook.
1603 */
1595 */
1604 Notebook.prototype.toJSON = function () {
1596 Notebook.prototype.toJSON = function () {
1605 var cells = this.get_cells();
1597 var cells = this.get_cells();
1606 var ncells = cells.length;
1598 var ncells = cells.length;
1607 var cell_array = new Array(ncells);
1599 var cell_array = new Array(ncells);
1608 for (var i=0; i<ncells; i++) {
1600 for (var i=0; i<ncells; i++) {
1609 cell_array[i] = cells[i].toJSON();
1601 cell_array[i] = cells[i].toJSON();
1610 };
1602 };
1611 var data = {
1603 var data = {
1612 // Only handle 1 worksheet for now.
1604 // Only handle 1 worksheet for now.
1613 worksheets : [{
1605 worksheets : [{
1614 cells: cell_array,
1606 cells: cell_array,
1615 metadata: this.worksheet_metadata
1607 metadata: this.worksheet_metadata
1616 }],
1608 }],
1617 metadata : this.metadata
1609 metadata : this.metadata
1618 };
1610 };
1619 return data;
1611 return data;
1620 };
1612 };
1621
1613
1622 /**
1614 /**
1623 * Start an autosave timer, for periodically saving the notebook.
1615 * Start an autosave timer, for periodically saving the notebook.
1624 *
1616 *
1625 * @method set_autosave_interval
1617 * @method set_autosave_interval
1626 * @param {Integer} interval the autosave interval in milliseconds
1618 * @param {Integer} interval the autosave interval in milliseconds
1627 */
1619 */
1628 Notebook.prototype.set_autosave_interval = function (interval) {
1620 Notebook.prototype.set_autosave_interval = function (interval) {
1629 var that = this;
1621 var that = this;
1630 // clear previous interval, so we don't get simultaneous timers
1622 // clear previous interval, so we don't get simultaneous timers
1631 if (this.autosave_timer) {
1623 if (this.autosave_timer) {
1632 clearInterval(this.autosave_timer);
1624 clearInterval(this.autosave_timer);
1633 }
1625 }
1634
1626
1635 this.autosave_interval = this.minimum_autosave_interval = interval;
1627 this.autosave_interval = this.minimum_autosave_interval = interval;
1636 if (interval) {
1628 if (interval) {
1637 this.autosave_timer = setInterval(function() {
1629 this.autosave_timer = setInterval(function() {
1638 if (that.dirty) {
1630 if (that.dirty) {
1639 that.save_notebook();
1631 that.save_notebook();
1640 }
1632 }
1641 }, interval);
1633 }, interval);
1642 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1634 $([IPython.events]).trigger("autosave_enabled.Notebook", interval);
1643 } else {
1635 } else {
1644 this.autosave_timer = null;
1636 this.autosave_timer = null;
1645 $([IPython.events]).trigger("autosave_disabled.Notebook");
1637 $([IPython.events]).trigger("autosave_disabled.Notebook");
1646 };
1638 };
1647 };
1639 };
1648
1640
1649 /**
1641 /**
1650 * Save this notebook on the server.
1642 * Save this notebook on the server.
1651 *
1643 *
1652 * @method save_notebook
1644 * @method save_notebook
1653 */
1645 */
1654 Notebook.prototype.save_notebook = function (extra_settings) {
1646 Notebook.prototype.save_notebook = function (extra_settings) {
1655 // Create a JSON model to be sent to the server.
1647 // Create a JSON model to be sent to the server.
1656 var model = {};
1648 var model = {};
1657 model.name = this.notebook_name;
1649 model.name = this.notebook_name;
1658 model.path = this.notebook_path;
1650 model.path = this.notebook_path;
1659 model.content = this.toJSON();
1651 model.content = this.toJSON();
1660 model.content.nbformat = this.nbformat;
1652 model.content.nbformat = this.nbformat;
1661 model.content.nbformat_minor = this.nbformat_minor;
1653 model.content.nbformat_minor = this.nbformat_minor;
1662 // time the ajax call for autosave tuning purposes.
1654 // time the ajax call for autosave tuning purposes.
1663 var start = new Date().getTime();
1655 var start = new Date().getTime();
1664 // We do the call with settings so we can set cache to false.
1656 // We do the call with settings so we can set cache to false.
1665 var settings = {
1657 var settings = {
1666 processData : false,
1658 processData : false,
1667 cache : false,
1659 cache : false,
1668 type : "PUT",
1660 type : "PUT",
1669 data : JSON.stringify(model),
1661 data : JSON.stringify(model),
1670 headers : {'Content-Type': 'application/json'},
1662 headers : {'Content-Type': 'application/json'},
1671 success : $.proxy(this.save_notebook_success, this, start),
1663 success : $.proxy(this.save_notebook_success, this, start),
1672 error : $.proxy(this.save_notebook_error, this)
1664 error : $.proxy(this.save_notebook_error, this)
1673 };
1665 };
1674 if (extra_settings) {
1666 if (extra_settings) {
1675 for (var key in extra_settings) {
1667 for (var key in extra_settings) {
1676 settings[key] = extra_settings[key];
1668 settings[key] = extra_settings[key];
1677 }
1669 }
1678 }
1670 }
1679 $([IPython.events]).trigger('notebook_saving.Notebook');
1671 $([IPython.events]).trigger('notebook_saving.Notebook');
1680 var url = utils.url_join_encode(
1672 var url = utils.url_join_encode(
1681 this._baseProjectUrl,
1673 this._baseProjectUrl,
1682 'api/notebooks',
1674 'api/notebooks',
1683 this.notebook_path,
1675 this.notebook_path,
1684 this.notebook_name
1676 this.notebook_name
1685 );
1677 );
1686 $.ajax(url, settings);
1678 $.ajax(url, settings);
1687 };
1679 };
1688
1680
1689 /**
1681 /**
1690 * Success callback for saving a notebook.
1682 * Success callback for saving a notebook.
1691 *
1683 *
1692 * @method save_notebook_success
1684 * @method save_notebook_success
1693 * @param {Integer} start the time when the save request started
1685 * @param {Integer} start the time when the save request started
1694 * @param {Object} data JSON representation of a notebook
1686 * @param {Object} data JSON representation of a notebook
1695 * @param {String} status Description of response status
1687 * @param {String} status Description of response status
1696 * @param {jqXHR} xhr jQuery Ajax object
1688 * @param {jqXHR} xhr jQuery Ajax object
1697 */
1689 */
1698 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1690 Notebook.prototype.save_notebook_success = function (start, data, status, xhr) {
1699 this.set_dirty(false);
1691 this.set_dirty(false);
1700 $([IPython.events]).trigger('notebook_saved.Notebook');
1692 $([IPython.events]).trigger('notebook_saved.Notebook');
1701 this._update_autosave_interval(start);
1693 this._update_autosave_interval(start);
1702 if (this._checkpoint_after_save) {
1694 if (this._checkpoint_after_save) {
1703 this.create_checkpoint();
1695 this.create_checkpoint();
1704 this._checkpoint_after_save = false;
1696 this._checkpoint_after_save = false;
1705 };
1697 };
1706 };
1698 };
1707
1699
1708 /**
1700 /**
1709 * update the autosave interval based on how long the last save took
1701 * update the autosave interval based on how long the last save took
1710 *
1702 *
1711 * @method _update_autosave_interval
1703 * @method _update_autosave_interval
1712 * @param {Integer} timestamp when the save request started
1704 * @param {Integer} timestamp when the save request started
1713 */
1705 */
1714 Notebook.prototype._update_autosave_interval = function (start) {
1706 Notebook.prototype._update_autosave_interval = function (start) {
1715 var duration = (new Date().getTime() - start);
1707 var duration = (new Date().getTime() - start);
1716 if (this.autosave_interval) {
1708 if (this.autosave_interval) {
1717 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1709 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
1718 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1710 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
1719 // round to 10 seconds, otherwise we will be setting a new interval too often
1711 // round to 10 seconds, otherwise we will be setting a new interval too often
1720 interval = 10000 * Math.round(interval / 10000);
1712 interval = 10000 * Math.round(interval / 10000);
1721 // set new interval, if it's changed
1713 // set new interval, if it's changed
1722 if (interval != this.autosave_interval) {
1714 if (interval != this.autosave_interval) {
1723 this.set_autosave_interval(interval);
1715 this.set_autosave_interval(interval);
1724 }
1716 }
1725 }
1717 }
1726 };
1718 };
1727
1719
1728 /**
1720 /**
1729 * Failure callback for saving a notebook.
1721 * Failure callback for saving a notebook.
1730 *
1722 *
1731 * @method save_notebook_error
1723 * @method save_notebook_error
1732 * @param {jqXHR} xhr jQuery Ajax object
1724 * @param {jqXHR} xhr jQuery Ajax object
1733 * @param {String} status Description of response status
1725 * @param {String} status Description of response status
1734 * @param {String} error HTTP error message
1726 * @param {String} error HTTP error message
1735 */
1727 */
1736 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1728 Notebook.prototype.save_notebook_error = function (xhr, status, error) {
1737 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1729 $([IPython.events]).trigger('notebook_save_failed.Notebook', [xhr, status, error]);
1738 };
1730 };
1739
1731
1740 Notebook.prototype.new_notebook = function(){
1732 Notebook.prototype.new_notebook = function(){
1741 var path = this.notebook_path;
1733 var path = this.notebook_path;
1742 var base_project_url = this._baseProjectUrl;
1734 var base_project_url = this._baseProjectUrl;
1743 var settings = {
1735 var settings = {
1744 processData : false,
1736 processData : false,
1745 cache : false,
1737 cache : false,
1746 type : "POST",
1738 type : "POST",
1747 dataType : "json",
1739 dataType : "json",
1748 async : false,
1740 async : false,
1749 success : function (data, status, xhr){
1741 success : function (data, status, xhr){
1750 var notebook_name = data.name;
1742 var notebook_name = data.name;
1751 window.open(
1743 window.open(
1752 utils.url_join_encode(
1744 utils.url_join_encode(
1753 base_project_url,
1745 base_project_url,
1754 'notebooks',
1746 'notebooks',
1755 path,
1747 path,
1756 notebook_name
1748 notebook_name
1757 ),
1749 ),
1758 '_blank'
1750 '_blank'
1759 );
1751 );
1760 }
1752 }
1761 };
1753 };
1762 var url = utils.url_join_encode(
1754 var url = utils.url_join_encode(
1763 base_project_url,
1755 base_project_url,
1764 'api/notebooks',
1756 'api/notebooks',
1765 path
1757 path
1766 );
1758 );
1767 $.ajax(url,settings);
1759 $.ajax(url,settings);
1768 };
1760 };
1769
1761
1770
1762
1771 Notebook.prototype.copy_notebook = function(){
1763 Notebook.prototype.copy_notebook = function(){
1772 var path = this.notebook_path;
1764 var path = this.notebook_path;
1773 var base_project_url = this._baseProjectUrl;
1765 var base_project_url = this._baseProjectUrl;
1774 var settings = {
1766 var settings = {
1775 processData : false,
1767 processData : false,
1776 cache : false,
1768 cache : false,
1777 type : "POST",
1769 type : "POST",
1778 dataType : "json",
1770 dataType : "json",
1779 data : JSON.stringify({copy_from : this.notebook_name}),
1771 data : JSON.stringify({copy_from : this.notebook_name}),
1780 async : false,
1772 async : false,
1781 success : function (data, status, xhr) {
1773 success : function (data, status, xhr) {
1782 window.open(utils.url_join_encode(
1774 window.open(utils.url_join_encode(
1783 base_project_url,
1775 base_project_url,
1784 'notebooks',
1776 'notebooks',
1785 data.path,
1777 data.path,
1786 data.name
1778 data.name
1787 ), '_blank');
1779 ), '_blank');
1788 }
1780 }
1789 };
1781 };
1790 var url = utils.url_join_encode(
1782 var url = utils.url_join_encode(
1791 base_project_url,
1783 base_project_url,
1792 'api/notebooks',
1784 'api/notebooks',
1793 path
1785 path
1794 );
1786 );
1795 $.ajax(url,settings);
1787 $.ajax(url,settings);
1796 };
1788 };
1797
1789
1798 Notebook.prototype.rename = function (nbname) {
1790 Notebook.prototype.rename = function (nbname) {
1799 var that = this;
1791 var that = this;
1800 var data = {name: nbname + '.ipynb'};
1792 var data = {name: nbname + '.ipynb'};
1801 var settings = {
1793 var settings = {
1802 processData : false,
1794 processData : false,
1803 cache : false,
1795 cache : false,
1804 type : "PATCH",
1796 type : "PATCH",
1805 data : JSON.stringify(data),
1797 data : JSON.stringify(data),
1806 dataType: "json",
1798 dataType: "json",
1807 headers : {'Content-Type': 'application/json'},
1799 headers : {'Content-Type': 'application/json'},
1808 success : $.proxy(that.rename_success, this),
1800 success : $.proxy(that.rename_success, this),
1809 error : $.proxy(that.rename_error, this)
1801 error : $.proxy(that.rename_error, this)
1810 };
1802 };
1811 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1803 $([IPython.events]).trigger('rename_notebook.Notebook', data);
1812 var url = utils.url_join_encode(
1804 var url = utils.url_join_encode(
1813 this._baseProjectUrl,
1805 this._baseProjectUrl,
1814 'api/notebooks',
1806 'api/notebooks',
1815 this.notebook_path,
1807 this.notebook_path,
1816 this.notebook_name
1808 this.notebook_name
1817 );
1809 );
1818 $.ajax(url, settings);
1810 $.ajax(url, settings);
1819 };
1811 };
1820
1812
1821
1813
1822 Notebook.prototype.rename_success = function (json, status, xhr) {
1814 Notebook.prototype.rename_success = function (json, status, xhr) {
1823 this.notebook_name = json.name;
1815 this.notebook_name = json.name;
1824 var name = this.notebook_name;
1816 var name = this.notebook_name;
1825 var path = json.path;
1817 var path = json.path;
1826 this.session.rename_notebook(name, path);
1818 this.session.rename_notebook(name, path);
1827 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1819 $([IPython.events]).trigger('notebook_renamed.Notebook', json);
1828 }
1820 }
1829
1821
1830 Notebook.prototype.rename_error = function (xhr, status, error) {
1822 Notebook.prototype.rename_error = function (xhr, status, error) {
1831 var that = this;
1823 var that = this;
1832 var dialog = $('<div/>').append(
1824 var dialog = $('<div/>').append(
1833 $("<p/>").addClass("rename-message")
1825 $("<p/>").addClass("rename-message")
1834 .text('This notebook name already exists.')
1826 .text('This notebook name already exists.')
1835 )
1827 )
1836 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1828 $([IPython.events]).trigger('notebook_rename_failed.Notebook', [xhr, status, error]);
1837 IPython.dialog.modal({
1829 IPython.dialog.modal({
1838 title: "Notebook Rename Error!",
1830 title: "Notebook Rename Error!",
1839 body: dialog,
1831 body: dialog,
1840 buttons : {
1832 buttons : {
1841 "Cancel": {},
1833 "Cancel": {},
1842 "OK": {
1834 "OK": {
1843 class: "btn-primary",
1835 class: "btn-primary",
1844 click: function () {
1836 click: function () {
1845 IPython.save_widget.rename_notebook();
1837 IPython.save_widget.rename_notebook();
1846 }}
1838 }}
1847 },
1839 },
1848 open : function (event, ui) {
1840 open : function (event, ui) {
1849 var that = $(this);
1841 var that = $(this);
1850 // Upon ENTER, click the OK button.
1842 // Upon ENTER, click the OK button.
1851 that.find('input[type="text"]').keydown(function (event, ui) {
1843 that.find('input[type="text"]').keydown(function (event, ui) {
1852 if (event.which === utils.keycodes.ENTER) {
1844 if (event.which === utils.keycodes.ENTER) {
1853 that.find('.btn-primary').first().click();
1845 that.find('.btn-primary').first().click();
1854 }
1846 }
1855 });
1847 });
1856 that.find('input[type="text"]').focus();
1848 that.find('input[type="text"]').focus();
1857 }
1849 }
1858 });
1850 });
1859 }
1851 }
1860
1852
1861 /**
1853 /**
1862 * Request a notebook's data from the server.
1854 * Request a notebook's data from the server.
1863 *
1855 *
1864 * @method load_notebook
1856 * @method load_notebook
1865 * @param {String} notebook_name and path A notebook to load
1857 * @param {String} notebook_name and path A notebook to load
1866 */
1858 */
1867 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1859 Notebook.prototype.load_notebook = function (notebook_name, notebook_path) {
1868 var that = this;
1860 var that = this;
1869 this.notebook_name = notebook_name;
1861 this.notebook_name = notebook_name;
1870 this.notebook_path = notebook_path;
1862 this.notebook_path = notebook_path;
1871 // We do the call with settings so we can set cache to false.
1863 // We do the call with settings so we can set cache to false.
1872 var settings = {
1864 var settings = {
1873 processData : false,
1865 processData : false,
1874 cache : false,
1866 cache : false,
1875 type : "GET",
1867 type : "GET",
1876 dataType : "json",
1868 dataType : "json",
1877 success : $.proxy(this.load_notebook_success,this),
1869 success : $.proxy(this.load_notebook_success,this),
1878 error : $.proxy(this.load_notebook_error,this),
1870 error : $.proxy(this.load_notebook_error,this),
1879 };
1871 };
1880 $([IPython.events]).trigger('notebook_loading.Notebook');
1872 $([IPython.events]).trigger('notebook_loading.Notebook');
1881 var url = utils.url_join_encode(
1873 var url = utils.url_join_encode(
1882 this._baseProjectUrl,
1874 this._baseProjectUrl,
1883 'api/notebooks',
1875 'api/notebooks',
1884 this.notebook_path,
1876 this.notebook_path,
1885 this.notebook_name
1877 this.notebook_name
1886 );
1878 );
1887 $.ajax(url, settings);
1879 $.ajax(url, settings);
1888 };
1880 };
1889
1881
1890 /**
1882 /**
1891 * Success callback for loading a notebook from the server.
1883 * Success callback for loading a notebook from the server.
1892 *
1884 *
1893 * Load notebook data from the JSON response.
1885 * Load notebook data from the JSON response.
1894 *
1886 *
1895 * @method load_notebook_success
1887 * @method load_notebook_success
1896 * @param {Object} data JSON representation of a notebook
1888 * @param {Object} data JSON representation of a notebook
1897 * @param {String} status Description of response status
1889 * @param {String} status Description of response status
1898 * @param {jqXHR} xhr jQuery Ajax object
1890 * @param {jqXHR} xhr jQuery Ajax object
1899 */
1891 */
1900 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1892 Notebook.prototype.load_notebook_success = function (data, status, xhr) {
1901 this.fromJSON(data);
1893 this.fromJSON(data);
1902 if (this.ncells() === 0) {
1894 if (this.ncells() === 0) {
1903 this.insert_cell_below('code');
1895 this.insert_cell_below('code');
1904 this.select(0);
1896 this.select(0);
1905 this.edit_mode();
1897 this.edit_mode();
1906 } else {
1898 } else {
1907 this.select(0);
1899 this.select(0);
1908 this.command_mode();
1900 this.command_mode();
1909 };
1901 };
1910 this.set_dirty(false);
1902 this.set_dirty(false);
1911 this.scroll_to_top();
1903 this.scroll_to_top();
1912 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1904 if (data.orig_nbformat !== undefined && data.nbformat !== data.orig_nbformat) {
1913 var msg = "This notebook has been converted from an older " +
1905 var msg = "This notebook has been converted from an older " +
1914 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1906 "notebook format (v"+data.orig_nbformat+") to the current notebook " +
1915 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1907 "format (v"+data.nbformat+"). The next time you save this notebook, the " +
1916 "newer notebook format will be used and older versions of IPython " +
1908 "newer notebook format will be used and older versions of IPython " +
1917 "may not be able to read it. To keep the older version, close the " +
1909 "may not be able to read it. To keep the older version, close the " +
1918 "notebook without saving it.";
1910 "notebook without saving it.";
1919 IPython.dialog.modal({
1911 IPython.dialog.modal({
1920 title : "Notebook converted",
1912 title : "Notebook converted",
1921 body : msg,
1913 body : msg,
1922 buttons : {
1914 buttons : {
1923 OK : {
1915 OK : {
1924 class : "btn-primary"
1916 class : "btn-primary"
1925 }
1917 }
1926 }
1918 }
1927 });
1919 });
1928 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1920 } else if (data.orig_nbformat_minor !== undefined && data.nbformat_minor !== data.orig_nbformat_minor) {
1929 var that = this;
1921 var that = this;
1930 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1922 var orig_vs = 'v' + data.nbformat + '.' + data.orig_nbformat_minor;
1931 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1923 var this_vs = 'v' + data.nbformat + '.' + this.nbformat_minor;
1932 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1924 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
1933 this_vs + ". You can still work with this notebook, but some features " +
1925 this_vs + ". You can still work with this notebook, but some features " +
1934 "introduced in later notebook versions may not be available."
1926 "introduced in later notebook versions may not be available."
1935
1927
1936 IPython.dialog.modal({
1928 IPython.dialog.modal({
1937 title : "Newer Notebook",
1929 title : "Newer Notebook",
1938 body : msg,
1930 body : msg,
1939 buttons : {
1931 buttons : {
1940 OK : {
1932 OK : {
1941 class : "btn-danger"
1933 class : "btn-danger"
1942 }
1934 }
1943 }
1935 }
1944 });
1936 });
1945
1937
1946 }
1938 }
1947
1939
1948 // Create the session after the notebook is completely loaded to prevent
1940 // Create the session after the notebook is completely loaded to prevent
1949 // code execution upon loading, which is a security risk.
1941 // code execution upon loading, which is a security risk.
1950 if (this.session == null) {
1942 if (this.session == null) {
1951 this.start_session();
1943 this.start_session();
1952 }
1944 }
1953 // load our checkpoint list
1945 // load our checkpoint list
1954 this.list_checkpoints();
1946 this.list_checkpoints();
1955
1947
1956 // load toolbar state
1948 // load toolbar state
1957 if (this.metadata.celltoolbar) {
1949 if (this.metadata.celltoolbar) {
1958 IPython.CellToolbar.global_show();
1950 IPython.CellToolbar.global_show();
1959 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1951 IPython.CellToolbar.activate_preset(this.metadata.celltoolbar);
1960 }
1952 }
1961
1953
1962 $([IPython.events]).trigger('notebook_loaded.Notebook');
1954 $([IPython.events]).trigger('notebook_loaded.Notebook');
1963 };
1955 };
1964
1956
1965 /**
1957 /**
1966 * Failure callback for loading a notebook from the server.
1958 * Failure callback for loading a notebook from the server.
1967 *
1959 *
1968 * @method load_notebook_error
1960 * @method load_notebook_error
1969 * @param {jqXHR} xhr jQuery Ajax object
1961 * @param {jqXHR} xhr jQuery Ajax object
1970 * @param {String} status Description of response status
1962 * @param {String} status Description of response status
1971 * @param {String} error HTTP error message
1963 * @param {String} error HTTP error message
1972 */
1964 */
1973 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1965 Notebook.prototype.load_notebook_error = function (xhr, status, error) {
1974 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1966 $([IPython.events]).trigger('notebook_load_failed.Notebook', [xhr, status, error]);
1975 if (xhr.status === 400) {
1967 if (xhr.status === 400) {
1976 var msg = error;
1968 var msg = error;
1977 } else if (xhr.status === 500) {
1969 } else if (xhr.status === 500) {
1978 var msg = "An unknown error occurred while loading this notebook. " +
1970 var msg = "An unknown error occurred while loading this notebook. " +
1979 "This version can load notebook formats " +
1971 "This version can load notebook formats " +
1980 "v" + this.nbformat + " or earlier.";
1972 "v" + this.nbformat + " or earlier.";
1981 }
1973 }
1982 IPython.dialog.modal({
1974 IPython.dialog.modal({
1983 title: "Error loading notebook",
1975 title: "Error loading notebook",
1984 body : msg,
1976 body : msg,
1985 buttons : {
1977 buttons : {
1986 "OK": {}
1978 "OK": {}
1987 }
1979 }
1988 });
1980 });
1989 }
1981 }
1990
1982
1991 /********************* checkpoint-related *********************/
1983 /********************* checkpoint-related *********************/
1992
1984
1993 /**
1985 /**
1994 * Save the notebook then immediately create a checkpoint.
1986 * Save the notebook then immediately create a checkpoint.
1995 *
1987 *
1996 * @method save_checkpoint
1988 * @method save_checkpoint
1997 */
1989 */
1998 Notebook.prototype.save_checkpoint = function () {
1990 Notebook.prototype.save_checkpoint = function () {
1999 this._checkpoint_after_save = true;
1991 this._checkpoint_after_save = true;
2000 this.save_notebook();
1992 this.save_notebook();
2001 };
1993 };
2002
1994
2003 /**
1995 /**
2004 * Add a checkpoint for this notebook.
1996 * Add a checkpoint for this notebook.
2005 * for use as a callback from checkpoint creation.
1997 * for use as a callback from checkpoint creation.
2006 *
1998 *
2007 * @method add_checkpoint
1999 * @method add_checkpoint
2008 */
2000 */
2009 Notebook.prototype.add_checkpoint = function (checkpoint) {
2001 Notebook.prototype.add_checkpoint = function (checkpoint) {
2010 var found = false;
2002 var found = false;
2011 for (var i = 0; i < this.checkpoints.length; i++) {
2003 for (var i = 0; i < this.checkpoints.length; i++) {
2012 var existing = this.checkpoints[i];
2004 var existing = this.checkpoints[i];
2013 if (existing.id == checkpoint.id) {
2005 if (existing.id == checkpoint.id) {
2014 found = true;
2006 found = true;
2015 this.checkpoints[i] = checkpoint;
2007 this.checkpoints[i] = checkpoint;
2016 break;
2008 break;
2017 }
2009 }
2018 }
2010 }
2019 if (!found) {
2011 if (!found) {
2020 this.checkpoints.push(checkpoint);
2012 this.checkpoints.push(checkpoint);
2021 }
2013 }
2022 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2014 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2023 };
2015 };
2024
2016
2025 /**
2017 /**
2026 * List checkpoints for this notebook.
2018 * List checkpoints for this notebook.
2027 *
2019 *
2028 * @method list_checkpoints
2020 * @method list_checkpoints
2029 */
2021 */
2030 Notebook.prototype.list_checkpoints = function () {
2022 Notebook.prototype.list_checkpoints = function () {
2031 var url = utils.url_join_encode(
2023 var url = utils.url_join_encode(
2032 this._baseProjectUrl,
2024 this._baseProjectUrl,
2033 'api/notebooks',
2025 'api/notebooks',
2034 this.notebook_path,
2026 this.notebook_path,
2035 this.notebook_name,
2027 this.notebook_name,
2036 'checkpoints'
2028 'checkpoints'
2037 );
2029 );
2038 $.get(url).done(
2030 $.get(url).done(
2039 $.proxy(this.list_checkpoints_success, this)
2031 $.proxy(this.list_checkpoints_success, this)
2040 ).fail(
2032 ).fail(
2041 $.proxy(this.list_checkpoints_error, this)
2033 $.proxy(this.list_checkpoints_error, this)
2042 );
2034 );
2043 };
2035 };
2044
2036
2045 /**
2037 /**
2046 * Success callback for listing checkpoints.
2038 * Success callback for listing checkpoints.
2047 *
2039 *
2048 * @method list_checkpoint_success
2040 * @method list_checkpoint_success
2049 * @param {Object} data JSON representation of a checkpoint
2041 * @param {Object} data JSON representation of a checkpoint
2050 * @param {String} status Description of response status
2042 * @param {String} status Description of response status
2051 * @param {jqXHR} xhr jQuery Ajax object
2043 * @param {jqXHR} xhr jQuery Ajax object
2052 */
2044 */
2053 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2045 Notebook.prototype.list_checkpoints_success = function (data, status, xhr) {
2054 var data = $.parseJSON(data);
2046 var data = $.parseJSON(data);
2055 this.checkpoints = data;
2047 this.checkpoints = data;
2056 if (data.length) {
2048 if (data.length) {
2057 this.last_checkpoint = data[data.length - 1];
2049 this.last_checkpoint = data[data.length - 1];
2058 } else {
2050 } else {
2059 this.last_checkpoint = null;
2051 this.last_checkpoint = null;
2060 }
2052 }
2061 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2053 $([IPython.events]).trigger('checkpoints_listed.Notebook', [data]);
2062 };
2054 };
2063
2055
2064 /**
2056 /**
2065 * Failure callback for listing a checkpoint.
2057 * Failure callback for listing a checkpoint.
2066 *
2058 *
2067 * @method list_checkpoint_error
2059 * @method list_checkpoint_error
2068 * @param {jqXHR} xhr jQuery Ajax object
2060 * @param {jqXHR} xhr jQuery Ajax object
2069 * @param {String} status Description of response status
2061 * @param {String} status Description of response status
2070 * @param {String} error_msg HTTP error message
2062 * @param {String} error_msg HTTP error message
2071 */
2063 */
2072 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2064 Notebook.prototype.list_checkpoints_error = function (xhr, status, error_msg) {
2073 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2065 $([IPython.events]).trigger('list_checkpoints_failed.Notebook');
2074 };
2066 };
2075
2067
2076 /**
2068 /**
2077 * Create a checkpoint of this notebook on the server from the most recent save.
2069 * Create a checkpoint of this notebook on the server from the most recent save.
2078 *
2070 *
2079 * @method create_checkpoint
2071 * @method create_checkpoint
2080 */
2072 */
2081 Notebook.prototype.create_checkpoint = function () {
2073 Notebook.prototype.create_checkpoint = function () {
2082 var url = utils.url_join_encode(
2074 var url = utils.url_join_encode(
2083 this._baseProjectUrl,
2075 this._baseProjectUrl,
2084 'api/notebooks',
2076 'api/notebooks',
2085 this.notebookPath(),
2077 this.notebookPath(),
2086 this.notebook_name,
2078 this.notebook_name,
2087 'checkpoints'
2079 'checkpoints'
2088 );
2080 );
2089 $.post(url).done(
2081 $.post(url).done(
2090 $.proxy(this.create_checkpoint_success, this)
2082 $.proxy(this.create_checkpoint_success, this)
2091 ).fail(
2083 ).fail(
2092 $.proxy(this.create_checkpoint_error, this)
2084 $.proxy(this.create_checkpoint_error, this)
2093 );
2085 );
2094 };
2086 };
2095
2087
2096 /**
2088 /**
2097 * Success callback for creating a checkpoint.
2089 * Success callback for creating a checkpoint.
2098 *
2090 *
2099 * @method create_checkpoint_success
2091 * @method create_checkpoint_success
2100 * @param {Object} data JSON representation of a checkpoint
2092 * @param {Object} data JSON representation of a checkpoint
2101 * @param {String} status Description of response status
2093 * @param {String} status Description of response status
2102 * @param {jqXHR} xhr jQuery Ajax object
2094 * @param {jqXHR} xhr jQuery Ajax object
2103 */
2095 */
2104 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2096 Notebook.prototype.create_checkpoint_success = function (data, status, xhr) {
2105 var data = $.parseJSON(data);
2097 var data = $.parseJSON(data);
2106 this.add_checkpoint(data);
2098 this.add_checkpoint(data);
2107 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2099 $([IPython.events]).trigger('checkpoint_created.Notebook', data);
2108 };
2100 };
2109
2101
2110 /**
2102 /**
2111 * Failure callback for creating a checkpoint.
2103 * Failure callback for creating a checkpoint.
2112 *
2104 *
2113 * @method create_checkpoint_error
2105 * @method create_checkpoint_error
2114 * @param {jqXHR} xhr jQuery Ajax object
2106 * @param {jqXHR} xhr jQuery Ajax object
2115 * @param {String} status Description of response status
2107 * @param {String} status Description of response status
2116 * @param {String} error_msg HTTP error message
2108 * @param {String} error_msg HTTP error message
2117 */
2109 */
2118 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2110 Notebook.prototype.create_checkpoint_error = function (xhr, status, error_msg) {
2119 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2111 $([IPython.events]).trigger('checkpoint_failed.Notebook');
2120 };
2112 };
2121
2113
2122 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2114 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2123 var that = this;
2115 var that = this;
2124 var checkpoint = checkpoint || this.last_checkpoint;
2116 var checkpoint = checkpoint || this.last_checkpoint;
2125 if ( ! checkpoint ) {
2117 if ( ! checkpoint ) {
2126 console.log("restore dialog, but no checkpoint to restore to!");
2118 console.log("restore dialog, but no checkpoint to restore to!");
2127 return;
2119 return;
2128 }
2120 }
2129 var body = $('<div/>').append(
2121 var body = $('<div/>').append(
2130 $('<p/>').addClass("p-space").text(
2122 $('<p/>').addClass("p-space").text(
2131 "Are you sure you want to revert the notebook to " +
2123 "Are you sure you want to revert the notebook to " +
2132 "the latest checkpoint?"
2124 "the latest checkpoint?"
2133 ).append(
2125 ).append(
2134 $("<strong/>").text(
2126 $("<strong/>").text(
2135 " This cannot be undone."
2127 " This cannot be undone."
2136 )
2128 )
2137 )
2129 )
2138 ).append(
2130 ).append(
2139 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2131 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2140 ).append(
2132 ).append(
2141 $('<p/>').addClass("p-space").text(
2133 $('<p/>').addClass("p-space").text(
2142 Date(checkpoint.last_modified)
2134 Date(checkpoint.last_modified)
2143 ).css("text-align", "center")
2135 ).css("text-align", "center")
2144 );
2136 );
2145
2137
2146 IPython.dialog.modal({
2138 IPython.dialog.modal({
2147 title : "Revert notebook to checkpoint",
2139 title : "Revert notebook to checkpoint",
2148 body : body,
2140 body : body,
2149 buttons : {
2141 buttons : {
2150 Revert : {
2142 Revert : {
2151 class : "btn-danger",
2143 class : "btn-danger",
2152 click : function () {
2144 click : function () {
2153 that.restore_checkpoint(checkpoint.id);
2145 that.restore_checkpoint(checkpoint.id);
2154 }
2146 }
2155 },
2147 },
2156 Cancel : {}
2148 Cancel : {}
2157 }
2149 }
2158 });
2150 });
2159 }
2151 }
2160
2152
2161 /**
2153 /**
2162 * Restore the notebook to a checkpoint state.
2154 * Restore the notebook to a checkpoint state.
2163 *
2155 *
2164 * @method restore_checkpoint
2156 * @method restore_checkpoint
2165 * @param {String} checkpoint ID
2157 * @param {String} checkpoint ID
2166 */
2158 */
2167 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2159 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2168 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2160 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2169 var url = utils.url_join_encode(
2161 var url = utils.url_join_encode(
2170 this._baseProjectUrl,
2162 this._baseProjectUrl,
2171 'api/notebooks',
2163 'api/notebooks',
2172 this.notebookPath(),
2164 this.notebookPath(),
2173 this.notebook_name,
2165 this.notebook_name,
2174 'checkpoints',
2166 'checkpoints',
2175 checkpoint
2167 checkpoint
2176 );
2168 );
2177 $.post(url).done(
2169 $.post(url).done(
2178 $.proxy(this.restore_checkpoint_success, this)
2170 $.proxy(this.restore_checkpoint_success, this)
2179 ).fail(
2171 ).fail(
2180 $.proxy(this.restore_checkpoint_error, this)
2172 $.proxy(this.restore_checkpoint_error, this)
2181 );
2173 );
2182 };
2174 };
2183
2175
2184 /**
2176 /**
2185 * Success callback for restoring a notebook to a checkpoint.
2177 * Success callback for restoring a notebook to a checkpoint.
2186 *
2178 *
2187 * @method restore_checkpoint_success
2179 * @method restore_checkpoint_success
2188 * @param {Object} data (ignored, should be empty)
2180 * @param {Object} data (ignored, should be empty)
2189 * @param {String} status Description of response status
2181 * @param {String} status Description of response status
2190 * @param {jqXHR} xhr jQuery Ajax object
2182 * @param {jqXHR} xhr jQuery Ajax object
2191 */
2183 */
2192 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2184 Notebook.prototype.restore_checkpoint_success = function (data, status, xhr) {
2193 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2185 $([IPython.events]).trigger('checkpoint_restored.Notebook');
2194 this.load_notebook(this.notebook_name, this.notebook_path);
2186 this.load_notebook(this.notebook_name, this.notebook_path);
2195 };
2187 };
2196
2188
2197 /**
2189 /**
2198 * Failure callback for restoring a notebook to a checkpoint.
2190 * Failure callback for restoring a notebook to a checkpoint.
2199 *
2191 *
2200 * @method restore_checkpoint_error
2192 * @method restore_checkpoint_error
2201 * @param {jqXHR} xhr jQuery Ajax object
2193 * @param {jqXHR} xhr jQuery Ajax object
2202 * @param {String} status Description of response status
2194 * @param {String} status Description of response status
2203 * @param {String} error_msg HTTP error message
2195 * @param {String} error_msg HTTP error message
2204 */
2196 */
2205 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2197 Notebook.prototype.restore_checkpoint_error = function (xhr, status, error_msg) {
2206 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2198 $([IPython.events]).trigger('checkpoint_restore_failed.Notebook');
2207 };
2199 };
2208
2200
2209 /**
2201 /**
2210 * Delete a notebook checkpoint.
2202 * Delete a notebook checkpoint.
2211 *
2203 *
2212 * @method delete_checkpoint
2204 * @method delete_checkpoint
2213 * @param {String} checkpoint ID
2205 * @param {String} checkpoint ID
2214 */
2206 */
2215 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2207 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2216 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2208 $([IPython.events]).trigger('notebook_restoring.Notebook', checkpoint);
2217 var url = utils.url_join_encode(
2209 var url = utils.url_join_encode(
2218 this._baseProjectUrl,
2210 this._baseProjectUrl,
2219 'api/notebooks',
2211 'api/notebooks',
2220 this.notebookPath(),
2212 this.notebookPath(),
2221 this.notebook_name,
2213 this.notebook_name,
2222 'checkpoints',
2214 'checkpoints',
2223 checkpoint
2215 checkpoint
2224 );
2216 );
2225 $.ajax(url, {
2217 $.ajax(url, {
2226 type: 'DELETE',
2218 type: 'DELETE',
2227 success: $.proxy(this.delete_checkpoint_success, this),
2219 success: $.proxy(this.delete_checkpoint_success, this),
2228 error: $.proxy(this.delete_notebook_error,this)
2220 error: $.proxy(this.delete_notebook_error,this)
2229 });
2221 });
2230 };
2222 };
2231
2223
2232 /**
2224 /**
2233 * Success callback for deleting a notebook checkpoint
2225 * Success callback for deleting a notebook checkpoint
2234 *
2226 *
2235 * @method delete_checkpoint_success
2227 * @method delete_checkpoint_success
2236 * @param {Object} data (ignored, should be empty)
2228 * @param {Object} data (ignored, should be empty)
2237 * @param {String} status Description of response status
2229 * @param {String} status Description of response status
2238 * @param {jqXHR} xhr jQuery Ajax object
2230 * @param {jqXHR} xhr jQuery Ajax object
2239 */
2231 */
2240 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2232 Notebook.prototype.delete_checkpoint_success = function (data, status, xhr) {
2241 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2233 $([IPython.events]).trigger('checkpoint_deleted.Notebook', data);
2242 this.load_notebook(this.notebook_name, this.notebook_path);
2234 this.load_notebook(this.notebook_name, this.notebook_path);
2243 };
2235 };
2244
2236
2245 /**
2237 /**
2246 * Failure callback for deleting a notebook checkpoint.
2238 * Failure callback for deleting a notebook checkpoint.
2247 *
2239 *
2248 * @method delete_checkpoint_error
2240 * @method delete_checkpoint_error
2249 * @param {jqXHR} xhr jQuery Ajax object
2241 * @param {jqXHR} xhr jQuery Ajax object
2250 * @param {String} status Description of response status
2242 * @param {String} status Description of response status
2251 * @param {String} error_msg HTTP error message
2243 * @param {String} error_msg HTTP error message
2252 */
2244 */
2253 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2245 Notebook.prototype.delete_checkpoint_error = function (xhr, status, error_msg) {
2254 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2246 $([IPython.events]).trigger('checkpoint_delete_failed.Notebook');
2255 };
2247 };
2256
2248
2257
2249
2258 IPython.Notebook = Notebook;
2250 IPython.Notebook = Notebook;
2259
2251
2260
2252
2261 return IPython;
2253 return IPython;
2262
2254
2263 }(IPython));
2255 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now