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