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