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