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