##// END OF EJS Templates
Backing out all changes to the UI and notebook.js....
Nathan Heijermans -
Show More
@@ -1,389 +1,386 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/utils',
8 'base/js/utils',
9 'notebook/js/tour',
9 'notebook/js/tour',
10 'bootstrap',
10 'bootstrap',
11 'moment',
11 'moment',
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
12 ], function($, IPython, dialog, utils, tour, bootstrap, moment) {
13 "use strict";
13 "use strict";
14
14
15 var MenuBar = function (selector, options) {
15 var MenuBar = function (selector, options) {
16 /**
16 /**
17 * Constructor
17 * Constructor
18 *
18 *
19 * A MenuBar Class to generate the menubar of IPython notebook
19 * A MenuBar Class to generate the menubar of IPython notebook
20 *
20 *
21 * Parameters:
21 * Parameters:
22 * selector: string
22 * selector: string
23 * options: dictionary
23 * options: dictionary
24 * Dictionary of keyword arguments.
24 * Dictionary of keyword arguments.
25 * notebook: Notebook instance
25 * notebook: Notebook instance
26 * contents: ContentManager instance
26 * contents: ContentManager instance
27 * events: $(Events) instance
27 * events: $(Events) instance
28 * save_widget: SaveWidget instance
28 * save_widget: SaveWidget instance
29 * quick_help: QuickHelp instance
29 * quick_help: QuickHelp instance
30 * base_url : string
30 * base_url : string
31 * notebook_path : string
31 * notebook_path : string
32 * notebook_name : string
32 * notebook_name : string
33 */
33 */
34 options = options || {};
34 options = options || {};
35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
35 this.base_url = options.base_url || utils.get_body_data("baseUrl");
36 this.selector = selector;
36 this.selector = selector;
37 this.notebook = options.notebook;
37 this.notebook = options.notebook;
38 this.contents = options.contents;
38 this.contents = options.contents;
39 this.events = options.events;
39 this.events = options.events;
40 this.save_widget = options.save_widget;
40 this.save_widget = options.save_widget;
41 this.quick_help = options.quick_help;
41 this.quick_help = options.quick_help;
42
42
43 try {
43 try {
44 this.tour = new tour.Tour(this.notebook, this.events);
44 this.tour = new tour.Tour(this.notebook, this.events);
45 } catch (e) {
45 } catch (e) {
46 this.tour = undefined;
46 this.tour = undefined;
47 console.log("Failed to instantiate Notebook Tour", e);
47 console.log("Failed to instantiate Notebook Tour", e);
48 }
48 }
49
49
50 if (this.selector !== undefined) {
50 if (this.selector !== undefined) {
51 this.element = $(selector);
51 this.element = $(selector);
52 this.style();
52 this.style();
53 this.bind_events();
53 this.bind_events();
54 }
54 }
55 };
55 };
56
56
57 // TODO: This has definitively nothing to do with style ...
57 // TODO: This has definitively nothing to do with style ...
58 MenuBar.prototype.style = function () {
58 MenuBar.prototype.style = function () {
59 var that = this;
59 var that = this;
60 this.element.find("li").click(function (event, ui) {
60 this.element.find("li").click(function (event, ui) {
61 // The selected cell loses focus when the menu is entered, so we
61 // The selected cell loses focus when the menu is entered, so we
62 // re-select it upon selection.
62 // re-select it upon selection.
63 var i = that.notebook.get_selected_index();
63 var i = that.notebook.get_selected_index();
64 that.notebook.select(i);
64 that.notebook.select(i);
65 }
65 }
66 );
66 );
67 };
67 };
68
68
69 MenuBar.prototype._nbconvert = function (format, download) {
69 MenuBar.prototype._nbconvert = function (format, download) {
70 download = download || false;
70 download = download || false;
71 var notebook_path = this.notebook.notebook_path;
71 var notebook_path = this.notebook.notebook_path;
72 var url = utils.url_join_encode(
72 var url = utils.url_join_encode(
73 this.base_url,
73 this.base_url,
74 'nbconvert',
74 'nbconvert',
75 format,
75 format,
76 notebook_path
76 notebook_path
77 ) + "?download=" + download.toString();
77 ) + "?download=" + download.toString();
78
78
79 var w = window.open()
79 var w = window.open()
80 if (this.notebook.dirty) {
80 if (this.notebook.dirty) {
81 this.notebook.save_notebook().then(function() {
81 this.notebook.save_notebook().then(function() {
82 w.location = url;
82 w.location = url;
83 });
83 });
84 } else {
84 } else {
85 w.location = url;
85 w.location = url;
86 }
86 }
87 };
87 };
88
88
89 MenuBar.prototype._size_header = function() {
89 MenuBar.prototype._size_header = function() {
90 /**
90 /**
91 * Update header spacer size.
91 * Update header spacer size.
92 */
92 */
93 this.events.trigger('resize-header.Page');
93 this.events.trigger('resize-header.Page');
94 };
94 };
95
95
96 MenuBar.prototype.bind_events = function () {
96 MenuBar.prototype.bind_events = function () {
97 /**
97 /**
98 * File
98 * File
99 */
99 */
100 var that = this;
100 var that = this;
101 this.element.find('#new_notebook').click(function () {
101 this.element.find('#new_notebook').click(function () {
102 var w = window.open();
102 var w = window.open();
103 // Create a new notebook in the same path as the current
103 // Create a new notebook in the same path as the current
104 // notebook's path.
104 // notebook's path.
105 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
105 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
106 that.contents.new_untitled(parent, {type: "notebook"}).then(
106 that.contents.new_untitled(parent, {type: "notebook"}).then(
107 function (data) {
107 function (data) {
108 w.location = utils.url_join_encode(
108 w.location = utils.url_join_encode(
109 that.base_url, 'notebooks', data.path
109 that.base_url, 'notebooks', data.path
110 );
110 );
111 },
111 },
112 function(error) {
112 function(error) {
113 w.close();
113 w.close();
114 dialog.modal({
114 dialog.modal({
115 title : 'Creating Notebook Failed',
115 title : 'Creating Notebook Failed',
116 body : "The error was: " + error.message,
116 body : "The error was: " + error.message,
117 buttons : {'OK' : {'class' : 'btn-primary'}}
117 buttons : {'OK' : {'class' : 'btn-primary'}}
118 });
118 });
119 }
119 }
120 );
120 );
121 });
121 });
122 this.element.find('#open_notebook').click(function () {
122 this.element.find('#open_notebook').click(function () {
123 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
123 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
124 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
124 window.open(utils.url_join_encode(that.base_url, 'tree', parent));
125 });
125 });
126 this.element.find('#copy_notebook').click(function () {
126 this.element.find('#copy_notebook').click(function () {
127 that.notebook.copy_notebook();
127 that.notebook.copy_notebook();
128 return false;
128 return false;
129 });
129 });
130 this.element.find('#download_ipynb').click(function () {
130 this.element.find('#download_ipynb').click(function () {
131 var base_url = that.notebook.base_url;
131 var base_url = that.notebook.base_url;
132 var notebook_path = that.notebook.notebook_path;
132 var notebook_path = that.notebook.notebook_path;
133 if (that.notebook.dirty) {
133 if (that.notebook.dirty) {
134 that.notebook.save_notebook({async : false});
134 that.notebook.save_notebook({async : false});
135 }
135 }
136
136
137 var url = utils.url_join_encode(base_url, 'files', notebook_path);
137 var url = utils.url_join_encode(base_url, 'files', notebook_path);
138 window.open(url + '?download=1');
138 window.open(url + '?download=1');
139 });
139 });
140
140
141 this.element.find('#print_preview').click(function () {
141 this.element.find('#print_preview').click(function () {
142 that._nbconvert('html', false);
142 that._nbconvert('html', false);
143 });
143 });
144
144
145 this.element.find('#download_html').click(function () {
145 this.element.find('#download_html').click(function () {
146 that._nbconvert('html', true);
146 that._nbconvert('html', true);
147 });
147 });
148
148
149 this.element.find('#download_rst').click(function () {
149 this.element.find('#download_rst').click(function () {
150 that._nbconvert('rst', true);
150 that._nbconvert('rst', true);
151 });
151 });
152
152
153 this.element.find('#download_pdf').click(function () {
153 this.element.find('#download_pdf').click(function () {
154 that._nbconvert('pdf', true);
154 that._nbconvert('pdf', true);
155 });
155 });
156
156
157 this.element.find('#download_script').click(function () {
157 this.element.find('#download_script').click(function () {
158 that._nbconvert('script', true);
158 that._nbconvert('script', true);
159 });
159 });
160
160
161 this.element.find('#rename_notebook').click(function () {
161 this.element.find('#rename_notebook').click(function () {
162 that.save_widget.rename_notebook({notebook: that.notebook});
162 that.save_widget.rename_notebook({notebook: that.notebook});
163 });
163 });
164 this.element.find('#save_checkpoint').click(function () {
164 this.element.find('#save_checkpoint').click(function () {
165 that.notebook.save_checkpoint();
165 that.notebook.save_checkpoint();
166 });
166 });
167 this.element.find('#restore_checkpoint').click(function () {
167 this.element.find('#restore_checkpoint').click(function () {
168 });
168 });
169 this.element.find('#trust_notebook').click(function () {
169 this.element.find('#trust_notebook').click(function () {
170 that.notebook.trust_notebook();
170 that.notebook.trust_notebook();
171 });
171 });
172 this.events.on('trust_changed.Notebook', function (event, trusted) {
172 this.events.on('trust_changed.Notebook', function (event, trusted) {
173 if (trusted) {
173 if (trusted) {
174 that.element.find('#trust_notebook')
174 that.element.find('#trust_notebook')
175 .addClass("disabled")
175 .addClass("disabled")
176 .find("a").text("Trusted Notebook");
176 .find("a").text("Trusted Notebook");
177 } else {
177 } else {
178 that.element.find('#trust_notebook')
178 that.element.find('#trust_notebook')
179 .removeClass("disabled")
179 .removeClass("disabled")
180 .find("a").text("Trust Notebook");
180 .find("a").text("Trust Notebook");
181 }
181 }
182 });
182 });
183 this.element.find('#kill_and_exit').click(function () {
183 this.element.find('#kill_and_exit').click(function () {
184 var close_window = function () {
184 var close_window = function () {
185 /**
185 /**
186 * allow closing of new tabs in Chromium, impossible in FF
186 * allow closing of new tabs in Chromium, impossible in FF
187 */
187 */
188 window.open('', '_self', '');
188 window.open('', '_self', '');
189 window.close();
189 window.close();
190 };
190 };
191 // finish with close on success or failure
191 // finish with close on success or failure
192 that.notebook.session.delete(close_window, close_window);
192 that.notebook.session.delete(close_window, close_window);
193 });
193 });
194 // Edit
194 // Edit
195 this.element.find('#cut_cell').click(function () {
195 this.element.find('#cut_cell').click(function () {
196 that.notebook.cut_cell();
196 that.notebook.cut_cell();
197 });
197 });
198 this.element.find('#copy_cell').click(function () {
198 this.element.find('#copy_cell').click(function () {
199 that.notebook.copy_cell();
199 that.notebook.copy_cell();
200 });
200 });
201 this.element.find('#delete_cell').click(function () {
201 this.element.find('#delete_cell').click(function () {
202 that.notebook.delete_cell();
202 that.notebook.delete_cell();
203 });
203 });
204 this.element.find('#undelete_cell').click(function () {
204 this.element.find('#undelete_cell').click(function () {
205 that.notebook.undelete_cell();
205 that.notebook.undelete_cell();
206 });
206 });
207 this.element.find('#split_cell').click(function () {
207 this.element.find('#split_cell').click(function () {
208 that.notebook.split_cell();
208 that.notebook.split_cell();
209 });
209 });
210 this.element.find('#merge_cell_above').click(function () {
210 this.element.find('#merge_cell_above').click(function () {
211 that.notebook.merge_cell_above();
211 that.notebook.merge_cell_above();
212 });
212 });
213 this.element.find('#merge_cell_below').click(function () {
213 this.element.find('#merge_cell_below').click(function () {
214 that.notebook.merge_cell_below();
214 that.notebook.merge_cell_below();
215 });
215 });
216 this.element.find('#move_cell_up').click(function () {
216 this.element.find('#move_cell_up').click(function () {
217 that.notebook.move_cell_up();
217 that.notebook.move_cell_up();
218 });
218 });
219 this.element.find('#move_cell_down').click(function () {
219 this.element.find('#move_cell_down').click(function () {
220 that.notebook.move_cell_down();
220 that.notebook.move_cell_down();
221 });
221 });
222 this.element.find('#edit_nb_metadata').click(function () {
222 this.element.find('#edit_nb_metadata').click(function () {
223 that.notebook.edit_metadata({
223 that.notebook.edit_metadata({
224 notebook: that.notebook,
224 notebook: that.notebook,
225 keyboard_manager: that.notebook.keyboard_manager});
225 keyboard_manager: that.notebook.keyboard_manager});
226 });
226 });
227
227
228 // View
228 // View
229 this.element.find('#toggle_header').click(function () {
229 this.element.find('#toggle_header').click(function () {
230 $('div#header-container').toggle();
230 $('div#header-container').toggle();
231 that._size_header();
231 that._size_header();
232 });
232 });
233 this.element.find('#toggle_toolbar').click(function () {
233 this.element.find('#toggle_toolbar').click(function () {
234 $('div#maintoolbar').toggle();
234 $('div#maintoolbar').toggle();
235 that._size_header();
235 that._size_header();
236 });
236 });
237 // Insert
237 // Insert
238 this.element.find('#insert_cell_above').click(function () {
238 this.element.find('#insert_cell_above').click(function () {
239 that.notebook.insert_cell_above('code');
239 that.notebook.insert_cell_above('code');
240 that.notebook.select_prev();
240 that.notebook.select_prev();
241 });
241 });
242 this.element.find('#insert_cell_below').click(function () {
242 this.element.find('#insert_cell_below').click(function () {
243 that.notebook.insert_cell_below('code');
243 that.notebook.insert_cell_below('code');
244 that.notebook.select_next();
244 that.notebook.select_next();
245 });
245 });
246 // Cell
246 // Cell
247 this.element.find('#run_cell').click(function () {
247 this.element.find('#run_cell').click(function () {
248 that.notebook.execute_cell();
248 that.notebook.execute_cell();
249 });
249 });
250 this.element.find('#run_cell_select_below').click(function () {
250 this.element.find('#run_cell_select_below').click(function () {
251 that.notebook.execute_cell_and_select_below();
251 that.notebook.execute_cell_and_select_below();
252 });
252 });
253 this.element.find('#run_cell_insert_below').click(function () {
253 this.element.find('#run_cell_insert_below').click(function () {
254 that.notebook.execute_cell_and_insert_below();
254 that.notebook.execute_cell_and_insert_below();
255 });
255 });
256 this.element.find('#run_all_cells').click(function () {
256 this.element.find('#run_all_cells').click(function () {
257 that.notebook.execute_all_cells();
257 that.notebook.execute_all_cells();
258 });
258 });
259 this.element.find('#run_all_cells_above').click(function () {
259 this.element.find('#run_all_cells_above').click(function () {
260 that.notebook.execute_cells_above();
260 that.notebook.execute_cells_above();
261 });
261 });
262 this.element.find('#run_all_cells_below').click(function () {
262 this.element.find('#run_all_cells_below').click(function () {
263 that.notebook.execute_cells_below();
263 that.notebook.execute_cells_below();
264 });
264 });
265 this.element.find('#to_code').click(function () {
265 this.element.find('#to_code').click(function () {
266 that.notebook.to_code();
266 that.notebook.to_code();
267 });
267 });
268 this.element.find('#to_markdown').click(function () {
268 this.element.find('#to_markdown').click(function () {
269 that.notebook.to_markdown();
269 that.notebook.to_markdown();
270 });
270 });
271 this.element.find('#to_raw').click(function () {
271 this.element.find('#to_raw').click(function () {
272 that.notebook.to_raw();
272 that.notebook.to_raw();
273 });
273 });
274
274
275 this.element.find('#toggle_current_output').click(function () {
275 this.element.find('#toggle_current_output').click(function () {
276 that.notebook.toggle_output();
276 that.notebook.toggle_output();
277 });
277 });
278 this.element.find('#toggle_current_output_scroll').click(function () {
278 this.element.find('#toggle_current_output_scroll').click(function () {
279 that.notebook.toggle_output_scroll();
279 that.notebook.toggle_output_scroll();
280 });
280 });
281 this.element.find('#clear_current_output').click(function () {
281 this.element.find('#clear_current_output').click(function () {
282 that.notebook.clear_output();
282 that.notebook.clear_output();
283 });
283 });
284
284
285 this.element.find('#toggle_all_output').click(function () {
285 this.element.find('#toggle_all_output').click(function () {
286 that.notebook.toggle_all_output();
286 that.notebook.toggle_all_output();
287 });
287 });
288 this.element.find('#toggle_all_output_scroll').click(function () {
288 this.element.find('#toggle_all_output_scroll').click(function () {
289 that.notebook.toggle_all_output_scroll();
289 that.notebook.toggle_all_output_scroll();
290 });
290 });
291 this.element.find('#clear_all_output').click(function () {
291 this.element.find('#clear_all_output').click(function () {
292 that.notebook.clear_all_output();
292 that.notebook.clear_all_output();
293 });
293 });
294
294
295 // Kernel
295 // Kernel
296 this.element.find('#toggle_unsolicited').click(function() {
297 IPython.notebook.toggle_ignore_unsolicited_msgs();
298 });
299 this.element.find('#int_kernel').click(function () {
296 this.element.find('#int_kernel').click(function () {
300 that.notebook.kernel.interrupt();
297 that.notebook.kernel.interrupt();
301 });
298 });
302 this.element.find('#restart_kernel').click(function () {
299 this.element.find('#restart_kernel').click(function () {
303 that.notebook.restart_kernel();
300 that.notebook.restart_kernel();
304 });
301 });
305 this.element.find('#reconnect_kernel').click(function () {
302 this.element.find('#reconnect_kernel').click(function () {
306 that.notebook.kernel.reconnect();
303 that.notebook.kernel.reconnect();
307 });
304 });
308 // Help
305 // Help
309 if (this.tour) {
306 if (this.tour) {
310 this.element.find('#notebook_tour').click(function () {
307 this.element.find('#notebook_tour').click(function () {
311 that.tour.start();
308 that.tour.start();
312 });
309 });
313 } else {
310 } else {
314 this.element.find('#notebook_tour').addClass("disabled");
311 this.element.find('#notebook_tour').addClass("disabled");
315 }
312 }
316 this.element.find('#keyboard_shortcuts').click(function () {
313 this.element.find('#keyboard_shortcuts').click(function () {
317 that.quick_help.show_keyboard_shortcuts();
314 that.quick_help.show_keyboard_shortcuts();
318 });
315 });
319
316
320 this.update_restore_checkpoint(null);
317 this.update_restore_checkpoint(null);
321
318
322 this.events.on('checkpoints_listed.Notebook', function (event, data) {
319 this.events.on('checkpoints_listed.Notebook', function (event, data) {
323 that.update_restore_checkpoint(that.notebook.checkpoints);
320 that.update_restore_checkpoint(that.notebook.checkpoints);
324 });
321 });
325
322
326 this.events.on('checkpoint_created.Notebook', function (event, data) {
323 this.events.on('checkpoint_created.Notebook', function (event, data) {
327 that.update_restore_checkpoint(that.notebook.checkpoints);
324 that.update_restore_checkpoint(that.notebook.checkpoints);
328 });
325 });
329
326
330 this.events.on('notebook_loaded.Notebook', function() {
327 this.events.on('notebook_loaded.Notebook', function() {
331 var langinfo = that.notebook.metadata.language_info || {};
328 var langinfo = that.notebook.metadata.language_info || {};
332 that.update_nbconvert_script(langinfo);
329 that.update_nbconvert_script(langinfo);
333 });
330 });
334
331
335 this.events.on('kernel_ready.Kernel', function(event, data) {
332 this.events.on('kernel_ready.Kernel', function(event, data) {
336 var langinfo = data.kernel.info_reply.language_info || {};
333 var langinfo = data.kernel.info_reply.language_info || {};
337 that.update_nbconvert_script(langinfo);
334 that.update_nbconvert_script(langinfo);
338 });
335 });
339 };
336 };
340
337
341 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
338 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
342 var ul = this.element.find("#restore_checkpoint").find("ul");
339 var ul = this.element.find("#restore_checkpoint").find("ul");
343 ul.empty();
340 ul.empty();
344 if (!checkpoints || checkpoints.length === 0) {
341 if (!checkpoints || checkpoints.length === 0) {
345 ul.append(
342 ul.append(
346 $("<li/>")
343 $("<li/>")
347 .addClass("disabled")
344 .addClass("disabled")
348 .append(
345 .append(
349 $("<a/>")
346 $("<a/>")
350 .text("No checkpoints")
347 .text("No checkpoints")
351 )
348 )
352 );
349 );
353 return;
350 return;
354 }
351 }
355
352
356 var that = this;
353 var that = this;
357 checkpoints.map(function (checkpoint) {
354 checkpoints.map(function (checkpoint) {
358 var d = new Date(checkpoint.last_modified);
355 var d = new Date(checkpoint.last_modified);
359 ul.append(
356 ul.append(
360 $("<li/>").append(
357 $("<li/>").append(
361 $("<a/>")
358 $("<a/>")
362 .attr("href", "#")
359 .attr("href", "#")
363 .text(moment(d).format("LLLL"))
360 .text(moment(d).format("LLLL"))
364 .click(function () {
361 .click(function () {
365 that.notebook.restore_checkpoint_dialog(checkpoint);
362 that.notebook.restore_checkpoint_dialog(checkpoint);
366 })
363 })
367 )
364 )
368 );
365 );
369 });
366 });
370 };
367 };
371
368
372 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
369 MenuBar.prototype.update_nbconvert_script = function(langinfo) {
373 /**
370 /**
374 * Set the 'Download as foo' menu option for the relevant language.
371 * Set the 'Download as foo' menu option for the relevant language.
375 */
372 */
376 var el = this.element.find('#download_script');
373 var el = this.element.find('#download_script');
377 var that = this;
374 var that = this;
378
375
379 // Set menu entry text to e.g. "Python (.py)"
376 // Set menu entry text to e.g. "Python (.py)"
380 var langname = (langinfo.name || 'Script')
377 var langname = (langinfo.name || 'Script')
381 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
378 langname = langname.charAt(0).toUpperCase()+langname.substr(1) // Capitalise
382 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
379 el.find('a').text(langname + ' ('+(langinfo.file_extension || 'txt')+')');
383 };
380 };
384
381
385 // Backwards compatability.
382 // Backwards compatability.
386 IPython.MenuBar = MenuBar;
383 IPython.MenuBar = MenuBar;
387
384
388 return {'MenuBar': MenuBar};
385 return {'MenuBar': MenuBar};
389 });
386 });
@@ -1,2583 +1,2527 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/dialog',
8 'base/js/dialog',
9 'notebook/js/cell',
9 'notebook/js/cell',
10 'notebook/js/textcell',
10 'notebook/js/textcell',
11 'notebook/js/codecell',
11 'notebook/js/codecell',
12 'services/sessions/session',
12 'services/sessions/session',
13 'notebook/js/celltoolbar',
13 'notebook/js/celltoolbar',
14 'components/marked/lib/marked',
14 'components/marked/lib/marked',
15 'codemirror/lib/codemirror',
15 'codemirror/lib/codemirror',
16 'codemirror/addon/runmode/runmode',
16 'codemirror/addon/runmode/runmode',
17 'notebook/js/mathjaxutils',
17 'notebook/js/mathjaxutils',
18 'base/js/keyboard',
18 'base/js/keyboard',
19 'notebook/js/tooltip',
19 'notebook/js/tooltip',
20 'notebook/js/celltoolbarpresets/default',
20 'notebook/js/celltoolbarpresets/default',
21 'notebook/js/celltoolbarpresets/rawcell',
21 'notebook/js/celltoolbarpresets/rawcell',
22 'notebook/js/celltoolbarpresets/slideshow',
22 'notebook/js/celltoolbarpresets/slideshow',
23 'notebook/js/scrollmanager'
23 'notebook/js/scrollmanager'
24 ], function (
24 ], function (
25 IPython,
25 IPython,
26 $,
26 $,
27 utils,
27 utils,
28 dialog,
28 dialog,
29 cellmod,
29 cellmod,
30 textcell,
30 textcell,
31 codecell,
31 codecell,
32 session,
32 session,
33 celltoolbar,
33 celltoolbar,
34 marked,
34 marked,
35 CodeMirror,
35 CodeMirror,
36 runMode,
36 runMode,
37 mathjaxutils,
37 mathjaxutils,
38 keyboard,
38 keyboard,
39 tooltip,
39 tooltip,
40 default_celltoolbar,
40 default_celltoolbar,
41 rawcell_celltoolbar,
41 rawcell_celltoolbar,
42 slideshow_celltoolbar,
42 slideshow_celltoolbar,
43 scrollmanager
43 scrollmanager
44 ) {
44 ) {
45 "use strict";
45 "use strict";
46
46
47 var Notebook = function (selector, options) {
47 var Notebook = function (selector, options) {
48 /**
48 /**
49 * Constructor
49 * Constructor
50 *
50 *
51 * A notebook contains and manages cells.
51 * A notebook contains and manages cells.
52 *
52 *
53 * Parameters:
53 * Parameters:
54 * selector: string
54 * selector: string
55 * options: dictionary
55 * options: dictionary
56 * Dictionary of keyword arguments.
56 * Dictionary of keyword arguments.
57 * events: $(Events) instance
57 * events: $(Events) instance
58 * keyboard_manager: KeyboardManager instance
58 * keyboard_manager: KeyboardManager instance
59 * contents: Contents instance
59 * contents: Contents instance
60 * save_widget: SaveWidget instance
60 * save_widget: SaveWidget instance
61 * config: dictionary
61 * config: dictionary
62 * base_url : string
62 * base_url : string
63 * notebook_path : string
63 * notebook_path : string
64 * notebook_name : string
64 * notebook_name : string
65 */
65 */
66 this.config = utils.mergeopt(Notebook, options.config);
66 this.config = utils.mergeopt(Notebook, options.config);
67 this.base_url = options.base_url;
67 this.base_url = options.base_url;
68 this.notebook_path = options.notebook_path;
68 this.notebook_path = options.notebook_path;
69 this.notebook_name = options.notebook_name;
69 this.notebook_name = options.notebook_name;
70 this.events = options.events;
70 this.events = options.events;
71 this.keyboard_manager = options.keyboard_manager;
71 this.keyboard_manager = options.keyboard_manager;
72 this.contents = options.contents;
72 this.contents = options.contents;
73 this.save_widget = options.save_widget;
73 this.save_widget = options.save_widget;
74 this.tooltip = new tooltip.Tooltip(this.events);
74 this.tooltip = new tooltip.Tooltip(this.events);
75 this.ws_url = options.ws_url;
75 this.ws_url = options.ws_url;
76 this._session_starting = false;
76 this._session_starting = false;
77 this.default_cell_type = this.config.default_cell_type || 'code';
77 this.default_cell_type = this.config.default_cell_type || 'code';
78
78
79 // Create default scroll manager.
79 // Create default scroll manager.
80 this.scroll_manager = new scrollmanager.ScrollManager(this);
80 this.scroll_manager = new scrollmanager.ScrollManager(this);
81
81
82 // TODO: This code smells (and the other `= this` line a couple lines down)
82 // TODO: This code smells (and the other `= this` line a couple lines down)
83 // We need a better way to deal with circular instance references.
83 // We need a better way to deal with circular instance references.
84 this.keyboard_manager.notebook = this;
84 this.keyboard_manager.notebook = this;
85 this.save_widget.notebook = this;
85 this.save_widget.notebook = this;
86
86
87 mathjaxutils.init();
87 mathjaxutils.init();
88
88
89 if (marked) {
89 if (marked) {
90 marked.setOptions({
90 marked.setOptions({
91 gfm : true,
91 gfm : true,
92 tables: true,
92 tables: true,
93 // FIXME: probably want central config for CodeMirror theme when we have js config
93 // FIXME: probably want central config for CodeMirror theme when we have js config
94 langPrefix: "cm-s-ipython language-",
94 langPrefix: "cm-s-ipython language-",
95 highlight: function(code, lang, callback) {
95 highlight: function(code, lang, callback) {
96 if (!lang) {
96 if (!lang) {
97 // no language, no highlight
97 // no language, no highlight
98 if (callback) {
98 if (callback) {
99 callback(null, code);
99 callback(null, code);
100 return;
100 return;
101 } else {
101 } else {
102 return code;
102 return code;
103 }
103 }
104 }
104 }
105 utils.requireCodeMirrorMode(lang, function () {
105 utils.requireCodeMirrorMode(lang, function () {
106 var el = document.createElement("div");
106 var el = document.createElement("div");
107 var mode = CodeMirror.getMode({}, lang);
107 var mode = CodeMirror.getMode({}, lang);
108 if (!mode) {
108 if (!mode) {
109 console.log("No CodeMirror mode: " + lang);
109 console.log("No CodeMirror mode: " + lang);
110 callback(null, code);
110 callback(null, code);
111 return;
111 return;
112 }
112 }
113 try {
113 try {
114 CodeMirror.runMode(code, mode, el);
114 CodeMirror.runMode(code, mode, el);
115 callback(null, el.innerHTML);
115 callback(null, el.innerHTML);
116 } catch (err) {
116 } catch (err) {
117 console.log("Failed to highlight " + lang + " code", err);
117 console.log("Failed to highlight " + lang + " code", err);
118 callback(err, code);
118 callback(err, code);
119 }
119 }
120 }, function (err) {
120 }, function (err) {
121 console.log("No CodeMirror mode: " + lang);
121 console.log("No CodeMirror mode: " + lang);
122 callback(err, code);
122 callback(err, code);
123 });
123 });
124 }
124 }
125 });
125 });
126 }
126 }
127
127
128 this.element = $(selector);
128 this.element = $(selector);
129 this.element.scroll();
129 this.element.scroll();
130 this.element.data("notebook", this);
130 this.element.data("notebook", this);
131 this.next_prompt_number = 1;
131 this.next_prompt_number = 1;
132 this.session = null;
132 this.session = null;
133 this.kernel = null;
133 this.kernel = null;
134 this.clipboard = null;
134 this.clipboard = null;
135 this.undelete_backup = null;
135 this.undelete_backup = null;
136 this.undelete_index = null;
136 this.undelete_index = null;
137 this.undelete_below = false;
137 this.undelete_below = false;
138 this.paste_enabled = false;
138 this.paste_enabled = false;
139 this.writable = false;
139 this.writable = false;
140 this.include_other_output = true;
141 // It is important to start out in command mode to match the intial mode
140 // It is important to start out in command mode to match the intial mode
142 // of the KeyboardManager.
141 // of the KeyboardManager.
143 this.mode = 'command';
142 this.mode = 'command';
144 this.set_dirty(false);
143 this.set_dirty(false);
145 this.metadata = {};
144 this.metadata = {};
146 this._checkpoint_after_save = false;
145 this._checkpoint_after_save = false;
147 this.last_checkpoint = null;
146 this.last_checkpoint = null;
148 this.checkpoints = [];
147 this.checkpoints = [];
149 this.autosave_interval = 0;
148 this.autosave_interval = 0;
150 this.autosave_timer = null;
149 this.autosave_timer = null;
151 // autosave *at most* every two minutes
150 // autosave *at most* every two minutes
152 this.minimum_autosave_interval = 120000;
151 this.minimum_autosave_interval = 120000;
153 this.notebook_name_blacklist_re = /[\/\\:]/;
152 this.notebook_name_blacklist_re = /[\/\\:]/;
154 this.nbformat = 4; // Increment this when changing the nbformat
153 this.nbformat = 4; // Increment this when changing the nbformat
155 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
154 this.nbformat_minor = this.current_nbformat_minor = 0; // Increment this when changing the nbformat
156 this.codemirror_mode = 'ipython';
155 this.codemirror_mode = 'ipython';
157 this.create_elements();
156 this.create_elements();
158 this.bind_events();
157 this.bind_events();
159 this.kernel_selector = null;
158 this.kernel_selector = null;
160 this.dirty = null;
159 this.dirty = null;
161 this.trusted = null;
160 this.trusted = null;
162 this._fully_loaded = false;
161 this._fully_loaded = false;
163
162
164 // Trigger cell toolbar registration.
163 // Trigger cell toolbar registration.
165 default_celltoolbar.register(this);
164 default_celltoolbar.register(this);
166 rawcell_celltoolbar.register(this);
165 rawcell_celltoolbar.register(this);
167 slideshow_celltoolbar.register(this);
166 slideshow_celltoolbar.register(this);
168
167
169 // prevent assign to miss-typed properties.
168 // prevent assign to miss-typed properties.
170 Object.seal(this);
169 Object.seal(this);
171 };
170 };
172
171
173 Notebook.options_default = {
172 Notebook.options_default = {
174 // can be any cell type, or the special values of
173 // can be any cell type, or the special values of
175 // 'above', 'below', or 'selected' to get the value from another cell.
174 // 'above', 'below', or 'selected' to get the value from another cell.
176 Notebook: {
175 Notebook: {
177 default_cell_type: 'code'
176 default_cell_type: 'code'
178 }
177 }
179 };
178 };
180
179
181
180
182 /**
181 /**
183 * Create an HTML and CSS representation of the notebook.
182 * Create an HTML and CSS representation of the notebook.
184 *
183 *
185 * @method create_elements
184 * @method create_elements
186 */
185 */
187 Notebook.prototype.create_elements = function () {
186 Notebook.prototype.create_elements = function () {
188 var that = this;
187 var that = this;
189 this.element.attr('tabindex','-1');
188 this.element.attr('tabindex','-1');
190 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
189 this.container = $("<div/>").addClass("container").attr("id", "notebook-container");
191 // We add this end_space div to the end of the notebook div to:
190 // We add this end_space div to the end of the notebook div to:
192 // i) provide a margin between the last cell and the end of the notebook
191 // i) provide a margin between the last cell and the end of the notebook
193 // ii) to prevent the div from scrolling up when the last cell is being
192 // ii) to prevent the div from scrolling up when the last cell is being
194 // edited, but is too low on the page, which browsers will do automatically.
193 // edited, but is too low on the page, which browsers will do automatically.
195 var end_space = $('<div/>').addClass('end_space');
194 var end_space = $('<div/>').addClass('end_space');
196 end_space.dblclick(function (e) {
195 end_space.dblclick(function (e) {
197 var ncells = that.ncells();
196 var ncells = that.ncells();
198 that.insert_cell_below('code',ncells-1);
197 that.insert_cell_below('code',ncells-1);
199 });
198 });
200 this.element.append(this.container);
199 this.element.append(this.container);
201 this.container.append(end_space);
200 this.container.append(end_space);
202 };
201 };
203
202
204 /**
203 /**
205 * Bind JavaScript events: key presses and custom IPython events.
204 * Bind JavaScript events: key presses and custom IPython events.
206 *
205 *
207 * @method bind_events
206 * @method bind_events
208 */
207 */
209 Notebook.prototype.bind_events = function () {
208 Notebook.prototype.bind_events = function () {
210 var that = this;
209 var that = this;
211
210
212 this.events.on('set_next_input.Notebook', function (event, data) {
211 this.events.on('set_next_input.Notebook', function (event, data) {
213 if (data.replace) {
212 if (data.replace) {
214 data.cell.set_text(data.text);
213 data.cell.set_text(data.text);
215 data.cell.clear_output();
214 data.cell.clear_output();
216 } else {
215 } else {
217 var index = that.find_cell_index(data.cell);
216 var index = that.find_cell_index(data.cell);
218 var new_cell = that.insert_cell_below('code',index);
217 var new_cell = that.insert_cell_below('code',index);
219 new_cell.set_text(data.text);
218 new_cell.set_text(data.text);
220 }
219 }
221 that.dirty = true;
220 that.dirty = true;
222 });
221 });
223
222
224 this.events.on('unrecognized_cell.Cell', function () {
223 this.events.on('unrecognized_cell.Cell', function () {
225 that.warn_nbformat_minor();
224 that.warn_nbformat_minor();
226 });
225 });
227
226
228 this.events.on('unrecognized_output.OutputArea', function () {
227 this.events.on('unrecognized_output.OutputArea', function () {
229 that.warn_nbformat_minor();
228 that.warn_nbformat_minor();
230 });
229 });
231
230
232 this.events.on('set_dirty.Notebook', function (event, data) {
231 this.events.on('set_dirty.Notebook', function (event, data) {
233 that.dirty = data.value;
232 that.dirty = data.value;
234 });
233 });
235
234
236 this.events.on('trust_changed.Notebook', function (event, trusted) {
235 this.events.on('trust_changed.Notebook', function (event, trusted) {
237 that.trusted = trusted;
236 that.trusted = trusted;
238 });
237 });
239
238
240 this.events.on('select.Cell', function (event, data) {
239 this.events.on('select.Cell', function (event, data) {
241 var index = that.find_cell_index(data.cell);
240 var index = that.find_cell_index(data.cell);
242 that.select(index);
241 that.select(index);
243 });
242 });
244
243
245 this.events.on('edit_mode.Cell', function (event, data) {
244 this.events.on('edit_mode.Cell', function (event, data) {
246 that.handle_edit_mode(data.cell);
245 that.handle_edit_mode(data.cell);
247 });
246 });
248
247
249 this.events.on('command_mode.Cell', function (event, data) {
248 this.events.on('command_mode.Cell', function (event, data) {
250 that.handle_command_mode(data.cell);
249 that.handle_command_mode(data.cell);
251 });
250 });
252
251
253 this.events.on('spec_changed.Kernel', function(event, data) {
252 this.events.on('spec_changed.Kernel', function(event, data) {
254 that.metadata.kernelspec =
253 that.metadata.kernelspec =
255 {name: data.name, display_name: data.display_name};
254 {name: data.name, display_name: data.display_name};
256 });
255 });
257
256
258 this.events.on('kernel_ready.Kernel', function(event, data) {
257 this.events.on('kernel_ready.Kernel', function(event, data) {
259 var kinfo = data.kernel.info_reply;
258 var kinfo = data.kernel.info_reply;
260 var langinfo = kinfo.language_info || {};
259 var langinfo = kinfo.language_info || {};
261 that.metadata.language_info = langinfo;
260 that.metadata.language_info = langinfo;
262 // Mode 'null' should be plain, unhighlighted text.
261 // Mode 'null' should be plain, unhighlighted text.
263 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
262 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
264 that.set_codemirror_mode(cm_mode);
263 that.set_codemirror_mode(cm_mode);
265 });
264 });
266
265
267 var collapse_time = function (time) {
266 var collapse_time = function (time) {
268 var app_height = $('#ipython-main-app').height(); // content height
267 var app_height = $('#ipython-main-app').height(); // content height
269 var splitter_height = $('div#pager_splitter').outerHeight(true);
268 var splitter_height = $('div#pager_splitter').outerHeight(true);
270 var new_height = app_height - splitter_height;
269 var new_height = app_height - splitter_height;
271 that.element.animate({height : new_height + 'px'}, time);
270 that.element.animate({height : new_height + 'px'}, time);
272 };
271 };
273
272
274 this.element.bind('collapse_pager', function (event, extrap) {
273 this.element.bind('collapse_pager', function (event, extrap) {
275 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
274 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
276 collapse_time(time);
275 collapse_time(time);
277 });
276 });
278
277
279 var expand_time = function (time) {
278 var expand_time = function (time) {
280 var app_height = $('#ipython-main-app').height(); // content height
279 var app_height = $('#ipython-main-app').height(); // content height
281 var splitter_height = $('div#pager_splitter').outerHeight(true);
280 var splitter_height = $('div#pager_splitter').outerHeight(true);
282 var pager_height = $('div#pager').outerHeight(true);
281 var pager_height = $('div#pager').outerHeight(true);
283 var new_height = app_height - pager_height - splitter_height;
282 var new_height = app_height - pager_height - splitter_height;
284 that.element.animate({height : new_height + 'px'}, time);
283 that.element.animate({height : new_height + 'px'}, time);
285 };
284 };
286
285
287 this.element.bind('expand_pager', function (event, extrap) {
286 this.element.bind('expand_pager', function (event, extrap) {
288 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
287 var time = (extrap !== undefined) ? ((extrap.duration !== undefined ) ? extrap.duration : 'fast') : 'fast';
289 expand_time(time);
288 expand_time(time);
290 });
289 });
291
290
292 // Firefox 22 broke $(window).on("beforeunload")
291 // Firefox 22 broke $(window).on("beforeunload")
293 // I'm not sure why or how.
292 // I'm not sure why or how.
294 window.onbeforeunload = function (e) {
293 window.onbeforeunload = function (e) {
295 // TODO: Make killing the kernel configurable.
294 // TODO: Make killing the kernel configurable.
296 var kill_kernel = false;
295 var kill_kernel = false;
297 if (kill_kernel) {
296 if (kill_kernel) {
298 that.session.delete();
297 that.session.delete();
299 }
298 }
300 // if we are autosaving, trigger an autosave on nav-away.
299 // if we are autosaving, trigger an autosave on nav-away.
301 // still warn, because if we don't the autosave may fail.
300 // still warn, because if we don't the autosave may fail.
302 if (that.dirty) {
301 if (that.dirty) {
303 if ( that.autosave_interval ) {
302 if ( that.autosave_interval ) {
304 // schedule autosave in a timeout
303 // schedule autosave in a timeout
305 // this gives you a chance to forcefully discard changes
304 // this gives you a chance to forcefully discard changes
306 // by reloading the page if you *really* want to.
305 // by reloading the page if you *really* want to.
307 // the timer doesn't start until you *dismiss* the dialog.
306 // the timer doesn't start until you *dismiss* the dialog.
308 setTimeout(function () {
307 setTimeout(function () {
309 if (that.dirty) {
308 if (that.dirty) {
310 that.save_notebook();
309 that.save_notebook();
311 }
310 }
312 }, 1000);
311 }, 1000);
313 return "Autosave in progress, latest changes may be lost.";
312 return "Autosave in progress, latest changes may be lost.";
314 } else {
313 } else {
315 return "Unsaved changes will be lost.";
314 return "Unsaved changes will be lost.";
316 }
315 }
317 }
316 }
318 // Null is the *only* return value that will make the browser not
317 // Null is the *only* return value that will make the browser not
319 // pop up the "don't leave" dialog.
318 // pop up the "don't leave" dialog.
320 return null;
319 return null;
321 };
320 };
322 };
321 };
323
322
324 Notebook.prototype.warn_nbformat_minor = function (event) {
323 Notebook.prototype.warn_nbformat_minor = function (event) {
325 /**
324 /**
326 * trigger a warning dialog about missing functionality from newer minor versions
325 * trigger a warning dialog about missing functionality from newer minor versions
327 */
326 */
328 var v = 'v' + this.nbformat + '.';
327 var v = 'v' + this.nbformat + '.';
329 var orig_vs = v + this.nbformat_minor;
328 var orig_vs = v + this.nbformat_minor;
330 var this_vs = v + this.current_nbformat_minor;
329 var this_vs = v + this.current_nbformat_minor;
331 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
330 var msg = "This notebook is version " + orig_vs + ", but we only fully support up to " +
332 this_vs + ". You can still work with this notebook, but cell and output types " +
331 this_vs + ". You can still work with this notebook, but cell and output types " +
333 "introduced in later notebook versions will not be available.";
332 "introduced in later notebook versions will not be available.";
334
333
335 dialog.modal({
334 dialog.modal({
336 notebook: this,
335 notebook: this,
337 keyboard_manager: this.keyboard_manager,
336 keyboard_manager: this.keyboard_manager,
338 title : "Newer Notebook",
337 title : "Newer Notebook",
339 body : msg,
338 body : msg,
340 buttons : {
339 buttons : {
341 OK : {
340 OK : {
342 "class" : "btn-danger"
341 "class" : "btn-danger"
343 }
342 }
344 }
343 }
345 });
344 });
346 }
345 }
347
346
348 /**
347 /**
349 * Set the dirty flag, and trigger the set_dirty.Notebook event
348 * Set the dirty flag, and trigger the set_dirty.Notebook event
350 *
349 *
351 * @method set_dirty
350 * @method set_dirty
352 */
351 */
353 Notebook.prototype.set_dirty = function (value) {
352 Notebook.prototype.set_dirty = function (value) {
354 if (value === undefined) {
353 if (value === undefined) {
355 value = true;
354 value = true;
356 }
355 }
357 if (this.dirty == value) {
356 if (this.dirty == value) {
358 return;
357 return;
359 }
358 }
360 this.events.trigger('set_dirty.Notebook', {value: value});
359 this.events.trigger('set_dirty.Notebook', {value: value});
361 };
360 };
362
361
363 /**
362 /**
364 * Scroll the top of the page to a given cell.
363 * Scroll the top of the page to a given cell.
365 *
364 *
366 * @method scroll_to_cell
365 * @method scroll_to_cell
367 * @param {Number} cell_number An index of the cell to view
366 * @param {Number} cell_number An index of the cell to view
368 * @param {Number} time Animation time in milliseconds
367 * @param {Number} time Animation time in milliseconds
369 * @return {Number} Pixel offset from the top of the container
368 * @return {Number} Pixel offset from the top of the container
370 */
369 */
371 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
370 Notebook.prototype.scroll_to_cell = function (cell_number, time) {
372 var cells = this.get_cells();
371 var cells = this.get_cells();
373 time = time || 0;
372 time = time || 0;
374 cell_number = Math.min(cells.length-1,cell_number);
373 cell_number = Math.min(cells.length-1,cell_number);
375 cell_number = Math.max(0 ,cell_number);
374 cell_number = Math.max(0 ,cell_number);
376 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
375 var scroll_value = cells[cell_number].element.position().top-cells[0].element.position().top ;
377 this.element.animate({scrollTop:scroll_value}, time);
376 this.element.animate({scrollTop:scroll_value}, time);
378 return scroll_value;
377 return scroll_value;
379 };
378 };
380
379
381 /**
380 /**
382 * Scroll to the bottom of the page.
381 * Scroll to the bottom of the page.
383 *
382 *
384 * @method scroll_to_bottom
383 * @method scroll_to_bottom
385 */
384 */
386 Notebook.prototype.scroll_to_bottom = function () {
385 Notebook.prototype.scroll_to_bottom = function () {
387 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
386 this.element.animate({scrollTop:this.element.get(0).scrollHeight}, 0);
388 };
387 };
389
388
390 /**
389 /**
391 * Scroll to the top of the page.
390 * Scroll to the top of the page.
392 *
391 *
393 * @method scroll_to_top
392 * @method scroll_to_top
394 */
393 */
395 Notebook.prototype.scroll_to_top = function () {
394 Notebook.prototype.scroll_to_top = function () {
396 this.element.animate({scrollTop:0}, 0);
395 this.element.animate({scrollTop:0}, 0);
397 };
396 };
398
397
399 // Edit Notebook metadata
398 // Edit Notebook metadata
400
399
401 Notebook.prototype.edit_metadata = function () {
400 Notebook.prototype.edit_metadata = function () {
402 var that = this;
401 var that = this;
403 dialog.edit_metadata({
402 dialog.edit_metadata({
404 md: this.metadata,
403 md: this.metadata,
405 callback: function (md) {
404 callback: function (md) {
406 that.metadata = md;
405 that.metadata = md;
407 },
406 },
408 name: 'Notebook',
407 name: 'Notebook',
409 notebook: this,
408 notebook: this,
410 keyboard_manager: this.keyboard_manager});
409 keyboard_manager: this.keyboard_manager});
411 };
410 };
412
411
413 // Cell indexing, retrieval, etc.
412 // Cell indexing, retrieval, etc.
414
413
415 /**
414 /**
416 * Get all cell elements in the notebook.
415 * Get all cell elements in the notebook.
417 *
416 *
418 * @method get_cell_elements
417 * @method get_cell_elements
419 * @return {jQuery} A selector of all cell elements
418 * @return {jQuery} A selector of all cell elements
420 */
419 */
421 Notebook.prototype.get_cell_elements = function () {
420 Notebook.prototype.get_cell_elements = function () {
422 return this.container.find(".cell").not('.cell .cell');
421 return this.container.find(".cell").not('.cell .cell');
423 };
422 };
424
423
425 /**
424 /**
426 * Get a particular cell element.
425 * Get a particular cell element.
427 *
426 *
428 * @method get_cell_element
427 * @method get_cell_element
429 * @param {Number} index An index of a cell to select
428 * @param {Number} index An index of a cell to select
430 * @return {jQuery} A selector of the given cell.
429 * @return {jQuery} A selector of the given cell.
431 */
430 */
432 Notebook.prototype.get_cell_element = function (index) {
431 Notebook.prototype.get_cell_element = function (index) {
433 var result = null;
432 var result = null;
434 var e = this.get_cell_elements().eq(index);
433 var e = this.get_cell_elements().eq(index);
435 if (e.length !== 0) {
434 if (e.length !== 0) {
436 result = e;
435 result = e;
437 }
436 }
438 return result;
437 return result;
439 };
438 };
440
439
441 /**
440 /**
442 * Try to get a particular cell by msg_id.
441 * Try to get a particular cell by msg_id.
443 *
442 *
444 * @method get_msg_cell
443 * @method get_msg_cell
445 * @param {String} msg_id A message UUID
444 * @param {String} msg_id A message UUID
446 * @return {Cell} Cell or null if no cell was found.
445 * @return {Cell} Cell or null if no cell was found.
447 */
446 */
448 Notebook.prototype.get_msg_cell = function (msg_id) {
447 Notebook.prototype.get_msg_cell = function (msg_id) {
449 return codecell.CodeCell.msg_cells[msg_id] || null;
448 return codecell.CodeCell.msg_cells[msg_id] || null;
450 };
449 };
451
450
452 /**
451 /**
453 * Count the cells in this notebook.
452 * Count the cells in this notebook.
454 *
453 *
455 * @method ncells
454 * @method ncells
456 * @return {Number} The number of cells in this notebook
455 * @return {Number} The number of cells in this notebook
457 */
456 */
458 Notebook.prototype.ncells = function () {
457 Notebook.prototype.ncells = function () {
459 return this.get_cell_elements().length;
458 return this.get_cell_elements().length;
460 };
459 };
461
460
462 /**
461 /**
463 * Get all Cell objects in this notebook.
462 * Get all Cell objects in this notebook.
464 *
463 *
465 * @method get_cells
464 * @method get_cells
466 * @return {Array} This notebook's Cell objects
465 * @return {Array} This notebook's Cell objects
467 */
466 */
468 // TODO: we are often calling cells as cells()[i], which we should optimize
467 // TODO: we are often calling cells as cells()[i], which we should optimize
469 // to cells(i) or a new method.
468 // to cells(i) or a new method.
470 Notebook.prototype.get_cells = function () {
469 Notebook.prototype.get_cells = function () {
471 return this.get_cell_elements().toArray().map(function (e) {
470 return this.get_cell_elements().toArray().map(function (e) {
472 return $(e).data("cell");
471 return $(e).data("cell");
473 });
472 });
474 };
473 };
475
474
476 /**
475 /**
477 * Get a Cell object from this notebook.
476 * Get a Cell object from this notebook.
478 *
477 *
479 * @method get_cell
478 * @method get_cell
480 * @param {Number} index An index of a cell to retrieve
479 * @param {Number} index An index of a cell to retrieve
481 * @return {Cell} Cell or null if no cell was found.
480 * @return {Cell} Cell or null if no cell was found.
482 */
481 */
483 Notebook.prototype.get_cell = function (index) {
482 Notebook.prototype.get_cell = function (index) {
484 var result = null;
483 var result = null;
485 var ce = this.get_cell_element(index);
484 var ce = this.get_cell_element(index);
486 if (ce !== null) {
485 if (ce !== null) {
487 result = ce.data('cell');
486 result = ce.data('cell');
488 }
487 }
489 return result;
488 return result;
490 };
489 };
491
490
492 /**
491 /**
493 * Get the cell below a given cell.
492 * Get the cell below a given cell.
494 *
493 *
495 * @method get_next_cell
494 * @method get_next_cell
496 * @param {Cell} cell The provided cell
495 * @param {Cell} cell The provided cell
497 * @return {Cell} the next cell or null if no cell was found.
496 * @return {Cell} the next cell or null if no cell was found.
498 */
497 */
499 Notebook.prototype.get_next_cell = function (cell) {
498 Notebook.prototype.get_next_cell = function (cell) {
500 var result = null;
499 var result = null;
501 var index = this.find_cell_index(cell);
500 var index = this.find_cell_index(cell);
502 if (this.is_valid_cell_index(index+1)) {
501 if (this.is_valid_cell_index(index+1)) {
503 result = this.get_cell(index+1);
502 result = this.get_cell(index+1);
504 }
503 }
505 return result;
504 return result;
506 };
505 };
507
506
508 /**
507 /**
509 * Get the cell above a given cell.
508 * Get the cell above a given cell.
510 *
509 *
511 * @method get_prev_cell
510 * @method get_prev_cell
512 * @param {Cell} cell The provided cell
511 * @param {Cell} cell The provided cell
513 * @return {Cell} The previous cell or null if no cell was found.
512 * @return {Cell} The previous cell or null if no cell was found.
514 */
513 */
515 Notebook.prototype.get_prev_cell = function (cell) {
514 Notebook.prototype.get_prev_cell = function (cell) {
516 var result = null;
515 var result = null;
517 var index = this.find_cell_index(cell);
516 var index = this.find_cell_index(cell);
518 if (index !== null && index > 0) {
517 if (index !== null && index > 0) {
519 result = this.get_cell(index-1);
518 result = this.get_cell(index-1);
520 }
519 }
521 return result;
520 return result;
522 };
521 };
523
522
524 /**
523 /**
525 * Get the numeric index of a given cell.
524 * Get the numeric index of a given cell.
526 *
525 *
527 * @method find_cell_index
526 * @method find_cell_index
528 * @param {Cell} cell The provided cell
527 * @param {Cell} cell The provided cell
529 * @return {Number} The cell's numeric index or null if no cell was found.
528 * @return {Number} The cell's numeric index or null if no cell was found.
530 */
529 */
531 Notebook.prototype.find_cell_index = function (cell) {
530 Notebook.prototype.find_cell_index = function (cell) {
532 var result = null;
531 var result = null;
533 this.get_cell_elements().filter(function (index) {
532 this.get_cell_elements().filter(function (index) {
534 if ($(this).data("cell") === cell) {
533 if ($(this).data("cell") === cell) {
535 result = index;
534 result = index;
536 }
535 }
537 });
536 });
538 return result;
537 return result;
539 };
538 };
540
539
541 /**
540 /**
542 * Get a given index , or the selected index if none is provided.
541 * Get a given index , or the selected index if none is provided.
543 *
542 *
544 * @method index_or_selected
543 * @method index_or_selected
545 * @param {Number} index A cell's index
544 * @param {Number} index A cell's index
546 * @return {Number} The given index, or selected index if none is provided.
545 * @return {Number} The given index, or selected index if none is provided.
547 */
546 */
548 Notebook.prototype.index_or_selected = function (index) {
547 Notebook.prototype.index_or_selected = function (index) {
549 var i;
548 var i;
550 if (index === undefined || index === null) {
549 if (index === undefined || index === null) {
551 i = this.get_selected_index();
550 i = this.get_selected_index();
552 if (i === null) {
551 if (i === null) {
553 i = 0;
552 i = 0;
554 }
553 }
555 } else {
554 } else {
556 i = index;
555 i = index;
557 }
556 }
558 return i;
557 return i;
559 };
558 };
560
559
561 /**
560 /**
562 * Get the currently selected cell.
561 * Get the currently selected cell.
563 * @method get_selected_cell
562 * @method get_selected_cell
564 * @return {Cell} The selected cell
563 * @return {Cell} The selected cell
565 */
564 */
566 Notebook.prototype.get_selected_cell = function () {
565 Notebook.prototype.get_selected_cell = function () {
567 var index = this.get_selected_index();
566 var index = this.get_selected_index();
568 return this.get_cell(index);
567 return this.get_cell(index);
569 };
568 };
570
569
571 /**
570 /**
572 * Check whether a cell index is valid.
571 * Check whether a cell index is valid.
573 *
572 *
574 * @method is_valid_cell_index
573 * @method is_valid_cell_index
575 * @param {Number} index A cell index
574 * @param {Number} index A cell index
576 * @return True if the index is valid, false otherwise
575 * @return True if the index is valid, false otherwise
577 */
576 */
578 Notebook.prototype.is_valid_cell_index = function (index) {
577 Notebook.prototype.is_valid_cell_index = function (index) {
579 if (index !== null && index >= 0 && index < this.ncells()) {
578 if (index !== null && index >= 0 && index < this.ncells()) {
580 return true;
579 return true;
581 } else {
580 } else {
582 return false;
581 return false;
583 }
582 }
584 };
583 };
585
584
586 /**
585 /**
587 * Get the index of the currently selected cell.
586 * Get the index of the currently selected cell.
588
587
589 * @method get_selected_index
588 * @method get_selected_index
590 * @return {Number} The selected cell's numeric index
589 * @return {Number} The selected cell's numeric index
591 */
590 */
592 Notebook.prototype.get_selected_index = function () {
591 Notebook.prototype.get_selected_index = function () {
593 var result = null;
592 var result = null;
594 this.get_cell_elements().filter(function (index) {
593 this.get_cell_elements().filter(function (index) {
595 if ($(this).data("cell").selected === true) {
594 if ($(this).data("cell").selected === true) {
596 result = index;
595 result = index;
597 }
596 }
598 });
597 });
599 return result;
598 return result;
600 };
599 };
601
600
602
601
603 // Cell selection.
602 // Cell selection.
604
603
605 /**
604 /**
606 * Programmatically select a cell.
605 * Programmatically select a cell.
607 *
606 *
608 * @method select
607 * @method select
609 * @param {Number} index A cell's index
608 * @param {Number} index A cell's index
610 * @return {Notebook} This notebook
609 * @return {Notebook} This notebook
611 */
610 */
612 Notebook.prototype.select = function (index) {
611 Notebook.prototype.select = function (index) {
613 if (this.is_valid_cell_index(index)) {
612 if (this.is_valid_cell_index(index)) {
614 var sindex = this.get_selected_index();
613 var sindex = this.get_selected_index();
615 if (sindex !== null && index !== sindex) {
614 if (sindex !== null && index !== sindex) {
616 // If we are about to select a different cell, make sure we are
615 // If we are about to select a different cell, make sure we are
617 // first in command mode.
616 // first in command mode.
618 if (this.mode !== 'command') {
617 if (this.mode !== 'command') {
619 this.command_mode();
618 this.command_mode();
620 }
619 }
621 this.get_cell(sindex).unselect();
620 this.get_cell(sindex).unselect();
622 }
621 }
623 var cell = this.get_cell(index);
622 var cell = this.get_cell(index);
624 cell.select();
623 cell.select();
625 if (cell.cell_type === 'heading') {
624 if (cell.cell_type === 'heading') {
626 this.events.trigger('selected_cell_type_changed.Notebook',
625 this.events.trigger('selected_cell_type_changed.Notebook',
627 {'cell_type':cell.cell_type,level:cell.level}
626 {'cell_type':cell.cell_type,level:cell.level}
628 );
627 );
629 } else {
628 } else {
630 this.events.trigger('selected_cell_type_changed.Notebook',
629 this.events.trigger('selected_cell_type_changed.Notebook',
631 {'cell_type':cell.cell_type}
630 {'cell_type':cell.cell_type}
632 );
631 );
633 }
632 }
634 }
633 }
635 return this;
634 return this;
636 };
635 };
637
636
638 /**
637 /**
639 * Programmatically select the next cell.
638 * Programmatically select the next cell.
640 *
639 *
641 * @method select_next
640 * @method select_next
642 * @return {Notebook} This notebook
641 * @return {Notebook} This notebook
643 */
642 */
644 Notebook.prototype.select_next = function () {
643 Notebook.prototype.select_next = function () {
645 var index = this.get_selected_index();
644 var index = this.get_selected_index();
646 this.select(index+1);
645 this.select(index+1);
647 return this;
646 return this;
648 };
647 };
649
648
650 /**
649 /**
651 * Programmatically select the previous cell.
650 * Programmatically select the previous cell.
652 *
651 *
653 * @method select_prev
652 * @method select_prev
654 * @return {Notebook} This notebook
653 * @return {Notebook} This notebook
655 */
654 */
656 Notebook.prototype.select_prev = function () {
655 Notebook.prototype.select_prev = function () {
657 var index = this.get_selected_index();
656 var index = this.get_selected_index();
658 this.select(index-1);
657 this.select(index-1);
659 return this;
658 return this;
660 };
659 };
661
660
662
661
663 // Edit/Command mode
662 // Edit/Command mode
664
663
665 /**
664 /**
666 * Gets the index of the cell that is in edit mode.
665 * Gets the index of the cell that is in edit mode.
667 *
666 *
668 * @method get_edit_index
667 * @method get_edit_index
669 *
668 *
670 * @return index {int}
669 * @return index {int}
671 **/
670 **/
672 Notebook.prototype.get_edit_index = function () {
671 Notebook.prototype.get_edit_index = function () {
673 var result = null;
672 var result = null;
674 this.get_cell_elements().filter(function (index) {
673 this.get_cell_elements().filter(function (index) {
675 if ($(this).data("cell").mode === 'edit') {
674 if ($(this).data("cell").mode === 'edit') {
676 result = index;
675 result = index;
677 }
676 }
678 });
677 });
679 return result;
678 return result;
680 };
679 };
681
680
682 /**
681 /**
683 * Handle when a a cell blurs and the notebook should enter command mode.
682 * Handle when a a cell blurs and the notebook should enter command mode.
684 *
683 *
685 * @method handle_command_mode
684 * @method handle_command_mode
686 * @param [cell] {Cell} Cell to enter command mode on.
685 * @param [cell] {Cell} Cell to enter command mode on.
687 **/
686 **/
688 Notebook.prototype.handle_command_mode = function (cell) {
687 Notebook.prototype.handle_command_mode = function (cell) {
689 if (this.mode !== 'command') {
688 if (this.mode !== 'command') {
690 cell.command_mode();
689 cell.command_mode();
691 this.mode = 'command';
690 this.mode = 'command';
692 this.events.trigger('command_mode.Notebook');
691 this.events.trigger('command_mode.Notebook');
693 this.keyboard_manager.command_mode();
692 this.keyboard_manager.command_mode();
694 }
693 }
695 };
694 };
696
695
697 /**
696 /**
698 * Make the notebook enter command mode.
697 * Make the notebook enter command mode.
699 *
698 *
700 * @method command_mode
699 * @method command_mode
701 **/
700 **/
702 Notebook.prototype.command_mode = function () {
701 Notebook.prototype.command_mode = function () {
703 var cell = this.get_cell(this.get_edit_index());
702 var cell = this.get_cell(this.get_edit_index());
704 if (cell && this.mode !== 'command') {
703 if (cell && this.mode !== 'command') {
705 // We don't call cell.command_mode, but rather call cell.focus_cell()
704 // We don't call cell.command_mode, but rather call cell.focus_cell()
706 // which will blur and CM editor and trigger the call to
705 // which will blur and CM editor and trigger the call to
707 // handle_command_mode.
706 // handle_command_mode.
708 cell.focus_cell();
707 cell.focus_cell();
709 }
708 }
710 };
709 };
711
710
712 /**
711 /**
713 * Handle when a cell fires it's edit_mode event.
712 * Handle when a cell fires it's edit_mode event.
714 *
713 *
715 * @method handle_edit_mode
714 * @method handle_edit_mode
716 * @param [cell] {Cell} Cell to enter edit mode on.
715 * @param [cell] {Cell} Cell to enter edit mode on.
717 **/
716 **/
718 Notebook.prototype.handle_edit_mode = function (cell) {
717 Notebook.prototype.handle_edit_mode = function (cell) {
719 if (cell && this.mode !== 'edit') {
718 if (cell && this.mode !== 'edit') {
720 cell.edit_mode();
719 cell.edit_mode();
721 this.mode = 'edit';
720 this.mode = 'edit';
722 this.events.trigger('edit_mode.Notebook');
721 this.events.trigger('edit_mode.Notebook');
723 this.keyboard_manager.edit_mode();
722 this.keyboard_manager.edit_mode();
724 }
723 }
725 };
724 };
726
725
727 /**
726 /**
728 * Make a cell enter edit mode.
727 * Make a cell enter edit mode.
729 *
728 *
730 * @method edit_mode
729 * @method edit_mode
731 **/
730 **/
732 Notebook.prototype.edit_mode = function () {
731 Notebook.prototype.edit_mode = function () {
733 var cell = this.get_selected_cell();
732 var cell = this.get_selected_cell();
734 if (cell && this.mode !== 'edit') {
733 if (cell && this.mode !== 'edit') {
735 cell.unrender();
734 cell.unrender();
736 cell.focus_editor();
735 cell.focus_editor();
737 }
736 }
738 };
737 };
739
738
740 /**
739 /**
741 * Focus the currently selected cell.
740 * Focus the currently selected cell.
742 *
741 *
743 * @method focus_cell
742 * @method focus_cell
744 **/
743 **/
745 Notebook.prototype.focus_cell = function () {
744 Notebook.prototype.focus_cell = function () {
746 var cell = this.get_selected_cell();
745 var cell = this.get_selected_cell();
747 if (cell === null) {return;} // No cell is selected
746 if (cell === null) {return;} // No cell is selected
748 cell.focus_cell();
747 cell.focus_cell();
749 };
748 };
750
749
751 // Cell movement
750 // Cell movement
752
751
753 /**
752 /**
754 * Move given (or selected) cell up and select it.
753 * Move given (or selected) cell up and select it.
755 *
754 *
756 * @method move_cell_up
755 * @method move_cell_up
757 * @param [index] {integer} cell index
756 * @param [index] {integer} cell index
758 * @return {Notebook} This notebook
757 * @return {Notebook} This notebook
759 **/
758 **/
760 Notebook.prototype.move_cell_up = function (index) {
759 Notebook.prototype.move_cell_up = function (index) {
761 var i = this.index_or_selected(index);
760 var i = this.index_or_selected(index);
762 if (this.is_valid_cell_index(i) && i > 0) {
761 if (this.is_valid_cell_index(i) && i > 0) {
763 var pivot = this.get_cell_element(i-1);
762 var pivot = this.get_cell_element(i-1);
764 var tomove = this.get_cell_element(i);
763 var tomove = this.get_cell_element(i);
765 if (pivot !== null && tomove !== null) {
764 if (pivot !== null && tomove !== null) {
766 tomove.detach();
765 tomove.detach();
767 pivot.before(tomove);
766 pivot.before(tomove);
768 this.select(i-1);
767 this.select(i-1);
769 var cell = this.get_selected_cell();
768 var cell = this.get_selected_cell();
770 cell.focus_cell();
769 cell.focus_cell();
771 }
770 }
772 this.set_dirty(true);
771 this.set_dirty(true);
773 }
772 }
774 return this;
773 return this;
775 };
774 };
776
775
777
776
778 /**
777 /**
779 * Move given (or selected) cell down and select it
778 * Move given (or selected) cell down and select it
780 *
779 *
781 * @method move_cell_down
780 * @method move_cell_down
782 * @param [index] {integer} cell index
781 * @param [index] {integer} cell index
783 * @return {Notebook} This notebook
782 * @return {Notebook} This notebook
784 **/
783 **/
785 Notebook.prototype.move_cell_down = function (index) {
784 Notebook.prototype.move_cell_down = function (index) {
786 var i = this.index_or_selected(index);
785 var i = this.index_or_selected(index);
787 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
786 if (this.is_valid_cell_index(i) && this.is_valid_cell_index(i+1)) {
788 var pivot = this.get_cell_element(i+1);
787 var pivot = this.get_cell_element(i+1);
789 var tomove = this.get_cell_element(i);
788 var tomove = this.get_cell_element(i);
790 if (pivot !== null && tomove !== null) {
789 if (pivot !== null && tomove !== null) {
791 tomove.detach();
790 tomove.detach();
792 pivot.after(tomove);
791 pivot.after(tomove);
793 this.select(i+1);
792 this.select(i+1);
794 var cell = this.get_selected_cell();
793 var cell = this.get_selected_cell();
795 cell.focus_cell();
794 cell.focus_cell();
796 }
795 }
797 }
796 }
798 this.set_dirty();
797 this.set_dirty();
799 return this;
798 return this;
800 };
799 };
801
800
802
801
803 // Insertion, deletion.
802 // Insertion, deletion.
804
803
805 /**
804 /**
806 * Delete a cell from the notebook.
805 * Delete a cell from the notebook.
807 *
806 *
808 * @method delete_cell
807 * @method delete_cell
809 * @param [index] A cell's numeric index
808 * @param [index] A cell's numeric index
810 * @return {Notebook} This notebook
809 * @return {Notebook} This notebook
811 */
810 */
812 Notebook.prototype.delete_cell = function (index) {
811 Notebook.prototype.delete_cell = function (index) {
813 var i = this.index_or_selected(index);
812 var i = this.index_or_selected(index);
814 var cell = this.get_cell(i);
813 var cell = this.get_cell(i);
815 if (!cell.is_deletable()) {
814 if (!cell.is_deletable()) {
816 return this;
815 return this;
817 }
816 }
818
817
819 this.undelete_backup = cell.toJSON();
818 this.undelete_backup = cell.toJSON();
820 $('#undelete_cell').removeClass('disabled');
819 $('#undelete_cell').removeClass('disabled');
821 if (this.is_valid_cell_index(i)) {
820 if (this.is_valid_cell_index(i)) {
822 var old_ncells = this.ncells();
821 var old_ncells = this.ncells();
823 var ce = this.get_cell_element(i);
822 var ce = this.get_cell_element(i);
824 ce.remove();
823 ce.remove();
825 if (i === 0) {
824 if (i === 0) {
826 // Always make sure we have at least one cell.
825 // Always make sure we have at least one cell.
827 if (old_ncells === 1) {
826 if (old_ncells === 1) {
828 this.insert_cell_below('code');
827 this.insert_cell_below('code');
829 }
828 }
830 this.select(0);
829 this.select(0);
831 this.undelete_index = 0;
830 this.undelete_index = 0;
832 this.undelete_below = false;
831 this.undelete_below = false;
833 } else if (i === old_ncells-1 && i !== 0) {
832 } else if (i === old_ncells-1 && i !== 0) {
834 this.select(i-1);
833 this.select(i-1);
835 this.undelete_index = i - 1;
834 this.undelete_index = i - 1;
836 this.undelete_below = true;
835 this.undelete_below = true;
837 } else {
836 } else {
838 this.select(i);
837 this.select(i);
839 this.undelete_index = i;
838 this.undelete_index = i;
840 this.undelete_below = false;
839 this.undelete_below = false;
841 }
840 }
842 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
841 this.events.trigger('delete.Cell', {'cell': cell, 'index': i});
843 this.set_dirty(true);
842 this.set_dirty(true);
844 }
843 }
845 return this;
844 return this;
846 };
845 };
847
846
848 /**
847 /**
849 * Restore the most recently deleted cell.
848 * Restore the most recently deleted cell.
850 *
849 *
851 * @method undelete
850 * @method undelete
852 */
851 */
853 Notebook.prototype.undelete_cell = function() {
852 Notebook.prototype.undelete_cell = function() {
854 if (this.undelete_backup !== null && this.undelete_index !== null) {
853 if (this.undelete_backup !== null && this.undelete_index !== null) {
855 var current_index = this.get_selected_index();
854 var current_index = this.get_selected_index();
856 if (this.undelete_index < current_index) {
855 if (this.undelete_index < current_index) {
857 current_index = current_index + 1;
856 current_index = current_index + 1;
858 }
857 }
859 if (this.undelete_index >= this.ncells()) {
858 if (this.undelete_index >= this.ncells()) {
860 this.select(this.ncells() - 1);
859 this.select(this.ncells() - 1);
861 }
860 }
862 else {
861 else {
863 this.select(this.undelete_index);
862 this.select(this.undelete_index);
864 }
863 }
865 var cell_data = this.undelete_backup;
864 var cell_data = this.undelete_backup;
866 var new_cell = null;
865 var new_cell = null;
867 if (this.undelete_below) {
866 if (this.undelete_below) {
868 new_cell = this.insert_cell_below(cell_data.cell_type);
867 new_cell = this.insert_cell_below(cell_data.cell_type);
869 } else {
868 } else {
870 new_cell = this.insert_cell_above(cell_data.cell_type);
869 new_cell = this.insert_cell_above(cell_data.cell_type);
871 }
870 }
872 new_cell.fromJSON(cell_data);
871 new_cell.fromJSON(cell_data);
873 if (this.undelete_below) {
872 if (this.undelete_below) {
874 this.select(current_index+1);
873 this.select(current_index+1);
875 } else {
874 } else {
876 this.select(current_index);
875 this.select(current_index);
877 }
876 }
878 this.undelete_backup = null;
877 this.undelete_backup = null;
879 this.undelete_index = null;
878 this.undelete_index = null;
880 }
879 }
881 $('#undelete_cell').addClass('disabled');
880 $('#undelete_cell').addClass('disabled');
882 };
881 };
883
882
884 /**
883 /**
885 * Insert a cell so that after insertion the cell is at given index.
884 * Insert a cell so that after insertion the cell is at given index.
886 *
885 *
887 * If cell type is not provided, it will default to the type of the
886 * If cell type is not provided, it will default to the type of the
888 * currently active cell.
887 * currently active cell.
889 *
888 *
890 * Similar to insert_above, but index parameter is mandatory
889 * Similar to insert_above, but index parameter is mandatory
891 *
890 *
892 * Index will be brought back into the accessible range [0,n]
891 * Index will be brought back into the accessible range [0,n]
893 *
892 *
894 * @method insert_cell_at_index
893 * @method insert_cell_at_index
895 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
894 * @param [type] {string} in ['code','markdown', 'raw'], defaults to 'code'
896 * @param [index] {int} a valid index where to insert cell
895 * @param [index] {int} a valid index where to insert cell
897 *
896 *
898 * @return cell {cell|null} created cell or null
897 * @return cell {cell|null} created cell or null
899 **/
898 **/
900 Notebook.prototype.insert_cell_at_index = function(type, index){
899 Notebook.prototype.insert_cell_at_index = function(type, index){
901
900
902 var ncells = this.ncells();
901 var ncells = this.ncells();
903 index = Math.min(index, ncells);
902 index = Math.min(index, ncells);
904 index = Math.max(index, 0);
903 index = Math.max(index, 0);
905 var cell = null;
904 var cell = null;
906 type = type || this.default_cell_type;
905 type = type || this.default_cell_type;
907 if (type === 'above') {
906 if (type === 'above') {
908 if (index > 0) {
907 if (index > 0) {
909 type = this.get_cell(index-1).cell_type;
908 type = this.get_cell(index-1).cell_type;
910 } else {
909 } else {
911 type = 'code';
910 type = 'code';
912 }
911 }
913 } else if (type === 'below') {
912 } else if (type === 'below') {
914 if (index < ncells) {
913 if (index < ncells) {
915 type = this.get_cell(index).cell_type;
914 type = this.get_cell(index).cell_type;
916 } else {
915 } else {
917 type = 'code';
916 type = 'code';
918 }
917 }
919 } else if (type === 'selected') {
918 } else if (type === 'selected') {
920 type = this.get_selected_cell().cell_type;
919 type = this.get_selected_cell().cell_type;
921 }
920 }
922
921
923 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
922 if (ncells === 0 || this.is_valid_cell_index(index) || index === ncells) {
924 var cell_options = {
923 var cell_options = {
925 events: this.events,
924 events: this.events,
926 config: this.config,
925 config: this.config,
927 keyboard_manager: this.keyboard_manager,
926 keyboard_manager: this.keyboard_manager,
928 notebook: this,
927 notebook: this,
929 tooltip: this.tooltip
928 tooltip: this.tooltip
930 };
929 };
931 switch(type) {
930 switch(type) {
932 case 'code':
931 case 'code':
933 cell = new codecell.CodeCell(this.kernel, cell_options);
932 cell = new codecell.CodeCell(this.kernel, cell_options);
934 cell.set_input_prompt();
933 cell.set_input_prompt();
935 break;
934 break;
936 case 'markdown':
935 case 'markdown':
937 cell = new textcell.MarkdownCell(cell_options);
936 cell = new textcell.MarkdownCell(cell_options);
938 break;
937 break;
939 case 'raw':
938 case 'raw':
940 cell = new textcell.RawCell(cell_options);
939 cell = new textcell.RawCell(cell_options);
941 break;
940 break;
942 default:
941 default:
943 console.log("Unrecognized cell type: ", type, cellmod);
942 console.log("Unrecognized cell type: ", type, cellmod);
944 cell = new cellmod.UnrecognizedCell(cell_options);
943 cell = new cellmod.UnrecognizedCell(cell_options);
945 }
944 }
946
945
947 if(this._insert_element_at_index(cell.element,index)) {
946 if(this._insert_element_at_index(cell.element,index)) {
948 cell.render();
947 cell.render();
949 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
948 this.events.trigger('create.Cell', {'cell': cell, 'index': index});
950 cell.refresh();
949 cell.refresh();
951 // We used to select the cell after we refresh it, but there
950 // We used to select the cell after we refresh it, but there
952 // are now cases were this method is called where select is
951 // are now cases were this method is called where select is
953 // not appropriate. The selection logic should be handled by the
952 // not appropriate. The selection logic should be handled by the
954 // caller of the the top level insert_cell methods.
953 // caller of the the top level insert_cell methods.
955 this.set_dirty(true);
954 this.set_dirty(true);
956 }
955 }
957 }
956 }
958 return cell;
957 return cell;
959
958
960 };
959 };
961
960
962 /**
961 /**
963 * Insert an element at given cell index.
962 * Insert an element at given cell index.
964 *
963 *
965 * @method _insert_element_at_index
964 * @method _insert_element_at_index
966 * @param element {dom_element} a cell element
965 * @param element {dom_element} a cell element
967 * @param [index] {int} a valid index where to inser cell
966 * @param [index] {int} a valid index where to inser cell
968 * @private
967 * @private
969 *
968 *
970 * return true if everything whent fine.
969 * return true if everything whent fine.
971 **/
970 **/
972 Notebook.prototype._insert_element_at_index = function(element, index){
971 Notebook.prototype._insert_element_at_index = function(element, index){
973 if (element === undefined){
972 if (element === undefined){
974 return false;
973 return false;
975 }
974 }
976
975
977 var ncells = this.ncells();
976 var ncells = this.ncells();
978
977
979 if (ncells === 0) {
978 if (ncells === 0) {
980 // special case append if empty
979 // special case append if empty
981 this.element.find('div.end_space').before(element);
980 this.element.find('div.end_space').before(element);
982 } else if ( ncells === index ) {
981 } else if ( ncells === index ) {
983 // special case append it the end, but not empty
982 // special case append it the end, but not empty
984 this.get_cell_element(index-1).after(element);
983 this.get_cell_element(index-1).after(element);
985 } else if (this.is_valid_cell_index(index)) {
984 } else if (this.is_valid_cell_index(index)) {
986 // otherwise always somewhere to append to
985 // otherwise always somewhere to append to
987 this.get_cell_element(index).before(element);
986 this.get_cell_element(index).before(element);
988 } else {
987 } else {
989 return false;
988 return false;
990 }
989 }
991
990
992 if (this.undelete_index !== null && index <= this.undelete_index) {
991 if (this.undelete_index !== null && index <= this.undelete_index) {
993 this.undelete_index = this.undelete_index + 1;
992 this.undelete_index = this.undelete_index + 1;
994 this.set_dirty(true);
993 this.set_dirty(true);
995 }
994 }
996 return true;
995 return true;
997 };
996 };
998
997
999 /**
998 /**
1000 * Insert a cell of given type above given index, or at top
999 * Insert a cell of given type above given index, or at top
1001 * of notebook if index smaller than 0.
1000 * of notebook if index smaller than 0.
1002 *
1001 *
1003 * default index value is the one of currently selected cell
1002 * default index value is the one of currently selected cell
1004 *
1003 *
1005 * @method insert_cell_above
1004 * @method insert_cell_above
1006 * @param [type] {string} cell type
1005 * @param [type] {string} cell type
1007 * @param [index] {integer}
1006 * @param [index] {integer}
1008 *
1007 *
1009 * @return handle to created cell or null
1008 * @return handle to created cell or null
1010 **/
1009 **/
1011 Notebook.prototype.insert_cell_above = function (type, index) {
1010 Notebook.prototype.insert_cell_above = function (type, index) {
1012 index = this.index_or_selected(index);
1011 index = this.index_or_selected(index);
1013 return this.insert_cell_at_index(type, index);
1012 return this.insert_cell_at_index(type, index);
1014 };
1013 };
1015
1014
1016 /**
1015 /**
1017 * Insert a cell of given type below given index, or at bottom
1016 * Insert a cell of given type below given index, or at bottom
1018 * of notebook if index greater than number of cells
1017 * of notebook if index greater than number of cells
1019 *
1018 *
1020 * default index value is the one of currently selected cell
1019 * default index value is the one of currently selected cell
1021 *
1020 *
1022 * @method insert_cell_below
1021 * @method insert_cell_below
1023 * @param [type] {string} cell type
1022 * @param [type] {string} cell type
1024 * @param [index] {integer}
1023 * @param [index] {integer}
1025 *
1024 *
1026 * @return handle to created cell or null
1025 * @return handle to created cell or null
1027 *
1026 *
1028 **/
1027 **/
1029 Notebook.prototype.insert_cell_below = function (type, index) {
1028 Notebook.prototype.insert_cell_below = function (type, index) {
1030 index = this.index_or_selected(index);
1029 index = this.index_or_selected(index);
1031 return this.insert_cell_at_index(type, index+1);
1030 return this.insert_cell_at_index(type, index+1);
1032 };
1031 };
1033
1032
1034
1033
1035 /**
1034 /**
1036 * Insert cell at end of notebook
1035 * Insert cell at end of notebook
1037 *
1036 *
1038 * @method insert_cell_at_bottom
1037 * @method insert_cell_at_bottom
1039 * @param {String} type cell type
1038 * @param {String} type cell type
1040 *
1039 *
1041 * @return the added cell; or null
1040 * @return the added cell; or null
1042 **/
1041 **/
1043 Notebook.prototype.insert_cell_at_bottom = function (type){
1042 Notebook.prototype.insert_cell_at_bottom = function (type){
1044 var len = this.ncells();
1043 var len = this.ncells();
1045 return this.insert_cell_below(type,len-1);
1044 return this.insert_cell_below(type,len-1);
1046 };
1045 };
1047
1046
1048 /**
1047 /**
1049 * Turn a cell into a code cell.
1048 * Turn a cell into a code cell.
1050 *
1049 *
1051 * @method to_code
1050 * @method to_code
1052 * @param {Number} [index] A cell's index
1051 * @param {Number} [index] A cell's index
1053 */
1052 */
1054 Notebook.prototype.to_code = function (index) {
1053 Notebook.prototype.to_code = function (index) {
1055 var i = this.index_or_selected(index);
1054 var i = this.index_or_selected(index);
1056 if (this.is_valid_cell_index(i)) {
1055 if (this.is_valid_cell_index(i)) {
1057 var source_cell = this.get_cell(i);
1056 var source_cell = this.get_cell(i);
1058 if (!(source_cell instanceof codecell.CodeCell)) {
1057 if (!(source_cell instanceof codecell.CodeCell)) {
1059 var target_cell = this.insert_cell_below('code',i);
1058 var target_cell = this.insert_cell_below('code',i);
1060 var text = source_cell.get_text();
1059 var text = source_cell.get_text();
1061 if (text === source_cell.placeholder) {
1060 if (text === source_cell.placeholder) {
1062 text = '';
1061 text = '';
1063 }
1062 }
1064 //metadata
1063 //metadata
1065 target_cell.metadata = source_cell.metadata;
1064 target_cell.metadata = source_cell.metadata;
1066
1065
1067 target_cell.set_text(text);
1066 target_cell.set_text(text);
1068 // make this value the starting point, so that we can only undo
1067 // make this value the starting point, so that we can only undo
1069 // to this state, instead of a blank cell
1068 // to this state, instead of a blank cell
1070 target_cell.code_mirror.clearHistory();
1069 target_cell.code_mirror.clearHistory();
1071 source_cell.element.remove();
1070 source_cell.element.remove();
1072 this.select(i);
1071 this.select(i);
1073 var cursor = source_cell.code_mirror.getCursor();
1072 var cursor = source_cell.code_mirror.getCursor();
1074 target_cell.code_mirror.setCursor(cursor);
1073 target_cell.code_mirror.setCursor(cursor);
1075 this.set_dirty(true);
1074 this.set_dirty(true);
1076 }
1075 }
1077 }
1076 }
1078 };
1077 };
1079
1078
1080 /**
1079 /**
1081 * Turn a cell into a Markdown cell.
1080 * Turn a cell into a Markdown cell.
1082 *
1081 *
1083 * @method to_markdown
1082 * @method to_markdown
1084 * @param {Number} [index] A cell's index
1083 * @param {Number} [index] A cell's index
1085 */
1084 */
1086 Notebook.prototype.to_markdown = function (index) {
1085 Notebook.prototype.to_markdown = function (index) {
1087 var i = this.index_or_selected(index);
1086 var i = this.index_or_selected(index);
1088 if (this.is_valid_cell_index(i)) {
1087 if (this.is_valid_cell_index(i)) {
1089 var source_cell = this.get_cell(i);
1088 var source_cell = this.get_cell(i);
1090
1089
1091 if (!(source_cell instanceof textcell.MarkdownCell)) {
1090 if (!(source_cell instanceof textcell.MarkdownCell)) {
1092 var target_cell = this.insert_cell_below('markdown',i);
1091 var target_cell = this.insert_cell_below('markdown',i);
1093 var text = source_cell.get_text();
1092 var text = source_cell.get_text();
1094
1093
1095 if (text === source_cell.placeholder) {
1094 if (text === source_cell.placeholder) {
1096 text = '';
1095 text = '';
1097 }
1096 }
1098 // metadata
1097 // metadata
1099 target_cell.metadata = source_cell.metadata;
1098 target_cell.metadata = source_cell.metadata;
1100 // We must show the editor before setting its contents
1099 // We must show the editor before setting its contents
1101 target_cell.unrender();
1100 target_cell.unrender();
1102 target_cell.set_text(text);
1101 target_cell.set_text(text);
1103 // make this value the starting point, so that we can only undo
1102 // make this value the starting point, so that we can only undo
1104 // to this state, instead of a blank cell
1103 // to this state, instead of a blank cell
1105 target_cell.code_mirror.clearHistory();
1104 target_cell.code_mirror.clearHistory();
1106 source_cell.element.remove();
1105 source_cell.element.remove();
1107 this.select(i);
1106 this.select(i);
1108 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1107 if ((source_cell instanceof textcell.TextCell) && source_cell.rendered) {
1109 target_cell.render();
1108 target_cell.render();
1110 }
1109 }
1111 var cursor = source_cell.code_mirror.getCursor();
1110 var cursor = source_cell.code_mirror.getCursor();
1112 target_cell.code_mirror.setCursor(cursor);
1111 target_cell.code_mirror.setCursor(cursor);
1113 this.set_dirty(true);
1112 this.set_dirty(true);
1114 }
1113 }
1115 }
1114 }
1116 };
1115 };
1117
1116
1118 /**
1117 /**
1119 * Turn a cell into a raw text cell.
1118 * Turn a cell into a raw text cell.
1120 *
1119 *
1121 * @method to_raw
1120 * @method to_raw
1122 * @param {Number} [index] A cell's index
1121 * @param {Number} [index] A cell's index
1123 */
1122 */
1124 Notebook.prototype.to_raw = function (index) {
1123 Notebook.prototype.to_raw = function (index) {
1125 var i = this.index_or_selected(index);
1124 var i = this.index_or_selected(index);
1126 if (this.is_valid_cell_index(i)) {
1125 if (this.is_valid_cell_index(i)) {
1127 var target_cell = null;
1126 var target_cell = null;
1128 var source_cell = this.get_cell(i);
1127 var source_cell = this.get_cell(i);
1129
1128
1130 if (!(source_cell instanceof textcell.RawCell)) {
1129 if (!(source_cell instanceof textcell.RawCell)) {
1131 target_cell = this.insert_cell_below('raw',i);
1130 target_cell = this.insert_cell_below('raw',i);
1132 var text = source_cell.get_text();
1131 var text = source_cell.get_text();
1133 if (text === source_cell.placeholder) {
1132 if (text === source_cell.placeholder) {
1134 text = '';
1133 text = '';
1135 }
1134 }
1136 //metadata
1135 //metadata
1137 target_cell.metadata = source_cell.metadata;
1136 target_cell.metadata = source_cell.metadata;
1138 // We must show the editor before setting its contents
1137 // We must show the editor before setting its contents
1139 target_cell.unrender();
1138 target_cell.unrender();
1140 target_cell.set_text(text);
1139 target_cell.set_text(text);
1141 // make this value the starting point, so that we can only undo
1140 // make this value the starting point, so that we can only undo
1142 // to this state, instead of a blank cell
1141 // to this state, instead of a blank cell
1143 target_cell.code_mirror.clearHistory();
1142 target_cell.code_mirror.clearHistory();
1144 source_cell.element.remove();
1143 source_cell.element.remove();
1145 this.select(i);
1144 this.select(i);
1146 var cursor = source_cell.code_mirror.getCursor();
1145 var cursor = source_cell.code_mirror.getCursor();
1147 target_cell.code_mirror.setCursor(cursor);
1146 target_cell.code_mirror.setCursor(cursor);
1148 this.set_dirty(true);
1147 this.set_dirty(true);
1149 }
1148 }
1150 }
1149 }
1151 };
1150 };
1152
1151
1153 Notebook.prototype._warn_heading = function () {
1152 Notebook.prototype._warn_heading = function () {
1154 /**
1153 /**
1155 * warn about heading cells being removed
1154 * warn about heading cells being removed
1156 */
1155 */
1157 dialog.modal({
1156 dialog.modal({
1158 notebook: this,
1157 notebook: this,
1159 keyboard_manager: this.keyboard_manager,
1158 keyboard_manager: this.keyboard_manager,
1160 title : "Use markdown headings",
1159 title : "Use markdown headings",
1161 body : $("<p/>").text(
1160 body : $("<p/>").text(
1162 'IPython no longer uses special heading cells. ' +
1161 'IPython no longer uses special heading cells. ' +
1163 'Instead, write your headings in Markdown cells using # characters:'
1162 'Instead, write your headings in Markdown cells using # characters:'
1164 ).append($('<pre/>').text(
1163 ).append($('<pre/>').text(
1165 '## This is a level 2 heading'
1164 '## This is a level 2 heading'
1166 )),
1165 )),
1167 buttons : {
1166 buttons : {
1168 "OK" : {}
1167 "OK" : {}
1169 }
1168 }
1170 });
1169 });
1171 };
1170 };
1172
1171
1173 /**
1172 /**
1174 * Turn a cell into a markdown cell with a heading.
1173 * Turn a cell into a markdown cell with a heading.
1175 *
1174 *
1176 * @method to_heading
1175 * @method to_heading
1177 * @param {Number} [index] A cell's index
1176 * @param {Number} [index] A cell's index
1178 * @param {Number} [level] A heading level (e.g., 1 for h1)
1177 * @param {Number} [level] A heading level (e.g., 1 for h1)
1179 */
1178 */
1180 Notebook.prototype.to_heading = function (index, level) {
1179 Notebook.prototype.to_heading = function (index, level) {
1181 this.to_markdown(index);
1180 this.to_markdown(index);
1182 level = level || 1;
1181 level = level || 1;
1183 var i = this.index_or_selected(index);
1182 var i = this.index_or_selected(index);
1184 if (this.is_valid_cell_index(i)) {
1183 if (this.is_valid_cell_index(i)) {
1185 var cell = this.get_cell(i);
1184 var cell = this.get_cell(i);
1186 cell.set_heading_level(level);
1185 cell.set_heading_level(level);
1187 this.set_dirty(true);
1186 this.set_dirty(true);
1188 }
1187 }
1189 };
1188 };
1190
1189
1191
1190
1192 // Cut/Copy/Paste
1191 // Cut/Copy/Paste
1193
1192
1194 /**
1193 /**
1195 * Enable UI elements for pasting cells.
1194 * Enable UI elements for pasting cells.
1196 *
1195 *
1197 * @method enable_paste
1196 * @method enable_paste
1198 */
1197 */
1199 Notebook.prototype.enable_paste = function () {
1198 Notebook.prototype.enable_paste = function () {
1200 var that = this;
1199 var that = this;
1201 if (!this.paste_enabled) {
1200 if (!this.paste_enabled) {
1202 $('#paste_cell_replace').removeClass('disabled')
1201 $('#paste_cell_replace').removeClass('disabled')
1203 .on('click', function () {that.paste_cell_replace();});
1202 .on('click', function () {that.paste_cell_replace();});
1204 $('#paste_cell_above').removeClass('disabled')
1203 $('#paste_cell_above').removeClass('disabled')
1205 .on('click', function () {that.paste_cell_above();});
1204 .on('click', function () {that.paste_cell_above();});
1206 $('#paste_cell_below').removeClass('disabled')
1205 $('#paste_cell_below').removeClass('disabled')
1207 .on('click', function () {that.paste_cell_below();});
1206 .on('click', function () {that.paste_cell_below();});
1208 this.paste_enabled = true;
1207 this.paste_enabled = true;
1209 }
1208 }
1210 };
1209 };
1211
1210
1212 /**
1211 /**
1213 * Disable UI elements for pasting cells.
1212 * Disable UI elements for pasting cells.
1214 *
1213 *
1215 * @method disable_paste
1214 * @method disable_paste
1216 */
1215 */
1217 Notebook.prototype.disable_paste = function () {
1216 Notebook.prototype.disable_paste = function () {
1218 if (this.paste_enabled) {
1217 if (this.paste_enabled) {
1219 $('#paste_cell_replace').addClass('disabled').off('click');
1218 $('#paste_cell_replace').addClass('disabled').off('click');
1220 $('#paste_cell_above').addClass('disabled').off('click');
1219 $('#paste_cell_above').addClass('disabled').off('click');
1221 $('#paste_cell_below').addClass('disabled').off('click');
1220 $('#paste_cell_below').addClass('disabled').off('click');
1222 this.paste_enabled = false;
1221 this.paste_enabled = false;
1223 }
1222 }
1224 };
1223 };
1225
1224
1226 /**
1225 /**
1227 * Cut a cell.
1226 * Cut a cell.
1228 *
1227 *
1229 * @method cut_cell
1228 * @method cut_cell
1230 */
1229 */
1231 Notebook.prototype.cut_cell = function () {
1230 Notebook.prototype.cut_cell = function () {
1232 this.copy_cell();
1231 this.copy_cell();
1233 this.delete_cell();
1232 this.delete_cell();
1234 };
1233 };
1235
1234
1236 /**
1235 /**
1237 * Copy a cell.
1236 * Copy a cell.
1238 *
1237 *
1239 * @method copy_cell
1238 * @method copy_cell
1240 */
1239 */
1241 Notebook.prototype.copy_cell = function () {
1240 Notebook.prototype.copy_cell = function () {
1242 var cell = this.get_selected_cell();
1241 var cell = this.get_selected_cell();
1243 this.clipboard = cell.toJSON();
1242 this.clipboard = cell.toJSON();
1244 // remove undeletable status from the copied cell
1243 // remove undeletable status from the copied cell
1245 if (this.clipboard.metadata.deletable !== undefined) {
1244 if (this.clipboard.metadata.deletable !== undefined) {
1246 delete this.clipboard.metadata.deletable;
1245 delete this.clipboard.metadata.deletable;
1247 }
1246 }
1248 this.enable_paste();
1247 this.enable_paste();
1249 };
1248 };
1250
1249
1251 /**
1250 /**
1252 * Replace the selected cell with a cell in the clipboard.
1251 * Replace the selected cell with a cell in the clipboard.
1253 *
1252 *
1254 * @method paste_cell_replace
1253 * @method paste_cell_replace
1255 */
1254 */
1256 Notebook.prototype.paste_cell_replace = function () {
1255 Notebook.prototype.paste_cell_replace = function () {
1257 if (this.clipboard !== null && this.paste_enabled) {
1256 if (this.clipboard !== null && this.paste_enabled) {
1258 var cell_data = this.clipboard;
1257 var cell_data = this.clipboard;
1259 var new_cell = this.insert_cell_above(cell_data.cell_type);
1258 var new_cell = this.insert_cell_above(cell_data.cell_type);
1260 new_cell.fromJSON(cell_data);
1259 new_cell.fromJSON(cell_data);
1261 var old_cell = this.get_next_cell(new_cell);
1260 var old_cell = this.get_next_cell(new_cell);
1262 this.delete_cell(this.find_cell_index(old_cell));
1261 this.delete_cell(this.find_cell_index(old_cell));
1263 this.select(this.find_cell_index(new_cell));
1262 this.select(this.find_cell_index(new_cell));
1264 }
1263 }
1265 };
1264 };
1266
1265
1267 /**
1266 /**
1268 * Paste a cell from the clipboard above the selected cell.
1267 * Paste a cell from the clipboard above the selected cell.
1269 *
1268 *
1270 * @method paste_cell_above
1269 * @method paste_cell_above
1271 */
1270 */
1272 Notebook.prototype.paste_cell_above = function () {
1271 Notebook.prototype.paste_cell_above = function () {
1273 if (this.clipboard !== null && this.paste_enabled) {
1272 if (this.clipboard !== null && this.paste_enabled) {
1274 var cell_data = this.clipboard;
1273 var cell_data = this.clipboard;
1275 var new_cell = this.insert_cell_above(cell_data.cell_type);
1274 var new_cell = this.insert_cell_above(cell_data.cell_type);
1276 new_cell.fromJSON(cell_data);
1275 new_cell.fromJSON(cell_data);
1277 new_cell.focus_cell();
1276 new_cell.focus_cell();
1278 }
1277 }
1279 };
1278 };
1280
1279
1281 /**
1280 /**
1282 * Paste a cell from the clipboard below the selected cell.
1281 * Paste a cell from the clipboard below the selected cell.
1283 *
1282 *
1284 * @method paste_cell_below
1283 * @method paste_cell_below
1285 */
1284 */
1286 Notebook.prototype.paste_cell_below = function () {
1285 Notebook.prototype.paste_cell_below = function () {
1287 if (this.clipboard !== null && this.paste_enabled) {
1286 if (this.clipboard !== null && this.paste_enabled) {
1288 var cell_data = this.clipboard;
1287 var cell_data = this.clipboard;
1289 var new_cell = this.insert_cell_below(cell_data.cell_type);
1288 var new_cell = this.insert_cell_below(cell_data.cell_type);
1290 new_cell.fromJSON(cell_data);
1289 new_cell.fromJSON(cell_data);
1291 new_cell.focus_cell();
1290 new_cell.focus_cell();
1292 }
1291 }
1293 };
1292 };
1294
1293
1295 // Split/merge
1294 // Split/merge
1296
1295
1297 /**
1296 /**
1298 * Split the selected cell into two, at the cursor.
1297 * Split the selected cell into two, at the cursor.
1299 *
1298 *
1300 * @method split_cell
1299 * @method split_cell
1301 */
1300 */
1302 Notebook.prototype.split_cell = function () {
1301 Notebook.prototype.split_cell = function () {
1303 var cell = this.get_selected_cell();
1302 var cell = this.get_selected_cell();
1304 if (cell.is_splittable()) {
1303 if (cell.is_splittable()) {
1305 var texta = cell.get_pre_cursor();
1304 var texta = cell.get_pre_cursor();
1306 var textb = cell.get_post_cursor();
1305 var textb = cell.get_post_cursor();
1307 cell.set_text(textb);
1306 cell.set_text(textb);
1308 var new_cell = this.insert_cell_above(cell.cell_type);
1307 var new_cell = this.insert_cell_above(cell.cell_type);
1309 // Unrender the new cell so we can call set_text.
1308 // Unrender the new cell so we can call set_text.
1310 new_cell.unrender();
1309 new_cell.unrender();
1311 new_cell.set_text(texta);
1310 new_cell.set_text(texta);
1312 }
1311 }
1313 };
1312 };
1314
1313
1315 /**
1314 /**
1316 * Combine the selected cell into the cell above it.
1315 * Combine the selected cell into the cell above it.
1317 *
1316 *
1318 * @method merge_cell_above
1317 * @method merge_cell_above
1319 */
1318 */
1320 Notebook.prototype.merge_cell_above = function () {
1319 Notebook.prototype.merge_cell_above = function () {
1321 var index = this.get_selected_index();
1320 var index = this.get_selected_index();
1322 var cell = this.get_cell(index);
1321 var cell = this.get_cell(index);
1323 var render = cell.rendered;
1322 var render = cell.rendered;
1324 if (!cell.is_mergeable()) {
1323 if (!cell.is_mergeable()) {
1325 return;
1324 return;
1326 }
1325 }
1327 if (index > 0) {
1326 if (index > 0) {
1328 var upper_cell = this.get_cell(index-1);
1327 var upper_cell = this.get_cell(index-1);
1329 if (!upper_cell.is_mergeable()) {
1328 if (!upper_cell.is_mergeable()) {
1330 return;
1329 return;
1331 }
1330 }
1332 var upper_text = upper_cell.get_text();
1331 var upper_text = upper_cell.get_text();
1333 var text = cell.get_text();
1332 var text = cell.get_text();
1334 if (cell instanceof codecell.CodeCell) {
1333 if (cell instanceof codecell.CodeCell) {
1335 cell.set_text(upper_text+'\n'+text);
1334 cell.set_text(upper_text+'\n'+text);
1336 } else {
1335 } else {
1337 cell.unrender(); // Must unrender before we set_text.
1336 cell.unrender(); // Must unrender before we set_text.
1338 cell.set_text(upper_text+'\n\n'+text);
1337 cell.set_text(upper_text+'\n\n'+text);
1339 if (render) {
1338 if (render) {
1340 // The rendered state of the final cell should match
1339 // The rendered state of the final cell should match
1341 // that of the original selected cell;
1340 // that of the original selected cell;
1342 cell.render();
1341 cell.render();
1343 }
1342 }
1344 }
1343 }
1345 this.delete_cell(index-1);
1344 this.delete_cell(index-1);
1346 this.select(this.find_cell_index(cell));
1345 this.select(this.find_cell_index(cell));
1347 }
1346 }
1348 };
1347 };
1349
1348
1350 /**
1349 /**
1351 * Combine the selected cell into the cell below it.
1350 * Combine the selected cell into the cell below it.
1352 *
1351 *
1353 * @method merge_cell_below
1352 * @method merge_cell_below
1354 */
1353 */
1355 Notebook.prototype.merge_cell_below = function () {
1354 Notebook.prototype.merge_cell_below = function () {
1356 var index = this.get_selected_index();
1355 var index = this.get_selected_index();
1357 var cell = this.get_cell(index);
1356 var cell = this.get_cell(index);
1358 var render = cell.rendered;
1357 var render = cell.rendered;
1359 if (!cell.is_mergeable()) {
1358 if (!cell.is_mergeable()) {
1360 return;
1359 return;
1361 }
1360 }
1362 if (index < this.ncells()-1) {
1361 if (index < this.ncells()-1) {
1363 var lower_cell = this.get_cell(index+1);
1362 var lower_cell = this.get_cell(index+1);
1364 if (!lower_cell.is_mergeable()) {
1363 if (!lower_cell.is_mergeable()) {
1365 return;
1364 return;
1366 }
1365 }
1367 var lower_text = lower_cell.get_text();
1366 var lower_text = lower_cell.get_text();
1368 var text = cell.get_text();
1367 var text = cell.get_text();
1369 if (cell instanceof codecell.CodeCell) {
1368 if (cell instanceof codecell.CodeCell) {
1370 cell.set_text(text+'\n'+lower_text);
1369 cell.set_text(text+'\n'+lower_text);
1371 } else {
1370 } else {
1372 cell.unrender(); // Must unrender before we set_text.
1371 cell.unrender(); // Must unrender before we set_text.
1373 cell.set_text(text+'\n\n'+lower_text);
1372 cell.set_text(text+'\n\n'+lower_text);
1374 if (render) {
1373 if (render) {
1375 // The rendered state of the final cell should match
1374 // The rendered state of the final cell should match
1376 // that of the original selected cell;
1375 // that of the original selected cell;
1377 cell.render();
1376 cell.render();
1378 }
1377 }
1379 }
1378 }
1380 this.delete_cell(index+1);
1379 this.delete_cell(index+1);
1381 this.select(this.find_cell_index(cell));
1380 this.select(this.find_cell_index(cell));
1382 }
1381 }
1383 };
1382 };
1384
1383
1385
1384
1386 // Cell collapsing and output clearing
1385 // Cell collapsing and output clearing
1387
1386
1388 /**
1387 /**
1389 * Hide a cell's output.
1388 * Hide a cell's output.
1390 *
1389 *
1391 * @method collapse_output
1390 * @method collapse_output
1392 * @param {Number} index A cell's numeric index
1391 * @param {Number} index A cell's numeric index
1393 */
1392 */
1394 Notebook.prototype.collapse_output = function (index) {
1393 Notebook.prototype.collapse_output = function (index) {
1395 var i = this.index_or_selected(index);
1394 var i = this.index_or_selected(index);
1396 var cell = this.get_cell(i);
1395 var cell = this.get_cell(i);
1397 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1396 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1398 cell.collapse_output();
1397 cell.collapse_output();
1399 this.set_dirty(true);
1398 this.set_dirty(true);
1400 }
1399 }
1401 };
1400 };
1402
1401
1403 /**
1402 /**
1404 * Hide each code cell's output area.
1403 * Hide each code cell's output area.
1405 *
1404 *
1406 * @method collapse_all_output
1405 * @method collapse_all_output
1407 */
1406 */
1408 Notebook.prototype.collapse_all_output = function () {
1407 Notebook.prototype.collapse_all_output = function () {
1409 this.get_cells().map(function (cell, i) {
1408 this.get_cells().map(function (cell, i) {
1410 if (cell instanceof codecell.CodeCell) {
1409 if (cell instanceof codecell.CodeCell) {
1411 cell.collapse_output();
1410 cell.collapse_output();
1412 }
1411 }
1413 });
1412 });
1414 // this should not be set if the `collapse` key is removed from nbformat
1413 // this should not be set if the `collapse` key is removed from nbformat
1415 this.set_dirty(true);
1414 this.set_dirty(true);
1416 };
1415 };
1417
1416
1418 /**
1417 /**
1419 * Show a cell's output.
1418 * Show a cell's output.
1420 *
1419 *
1421 * @method expand_output
1420 * @method expand_output
1422 * @param {Number} index A cell's numeric index
1421 * @param {Number} index A cell's numeric index
1423 */
1422 */
1424 Notebook.prototype.expand_output = function (index) {
1423 Notebook.prototype.expand_output = function (index) {
1425 var i = this.index_or_selected(index);
1424 var i = this.index_or_selected(index);
1426 var cell = this.get_cell(i);
1425 var cell = this.get_cell(i);
1427 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1426 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1428 cell.expand_output();
1427 cell.expand_output();
1429 this.set_dirty(true);
1428 this.set_dirty(true);
1430 }
1429 }
1431 };
1430 };
1432
1431
1433 /**
1432 /**
1434 * Expand each code cell's output area, and remove scrollbars.
1433 * Expand each code cell's output area, and remove scrollbars.
1435 *
1434 *
1436 * @method expand_all_output
1435 * @method expand_all_output
1437 */
1436 */
1438 Notebook.prototype.expand_all_output = function () {
1437 Notebook.prototype.expand_all_output = function () {
1439 this.get_cells().map(function (cell, i) {
1438 this.get_cells().map(function (cell, i) {
1440 if (cell instanceof codecell.CodeCell) {
1439 if (cell instanceof codecell.CodeCell) {
1441 cell.expand_output();
1440 cell.expand_output();
1442 }
1441 }
1443 });
1442 });
1444 // this should not be set if the `collapse` key is removed from nbformat
1443 // this should not be set if the `collapse` key is removed from nbformat
1445 this.set_dirty(true);
1444 this.set_dirty(true);
1446 };
1445 };
1447
1446
1448 /**
1447 /**
1449 * Clear the selected CodeCell's output area.
1448 * Clear the selected CodeCell's output area.
1450 *
1449 *
1451 * @method clear_output
1450 * @method clear_output
1452 * @param {Number} index A cell's numeric index
1451 * @param {Number} index A cell's numeric index
1453 */
1452 */
1454 Notebook.prototype.clear_output = function (index) {
1453 Notebook.prototype.clear_output = function (index) {
1455 var i = this.index_or_selected(index);
1454 var i = this.index_or_selected(index);
1456 var cell = this.get_cell(i);
1455 var cell = this.get_cell(i);
1457 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1456 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1458 cell.clear_output();
1457 cell.clear_output();
1459 this.set_dirty(true);
1458 this.set_dirty(true);
1460 }
1459 }
1461 };
1460 };
1462
1461
1463 /**
1462 /**
1464 * Clear each code cell's output area.
1463 * Clear each code cell's output area.
1465 *
1464 *
1466 * @method clear_all_output
1465 * @method clear_all_output
1467 */
1466 */
1468 Notebook.prototype.clear_all_output = function () {
1467 Notebook.prototype.clear_all_output = function () {
1469 this.get_cells().map(function (cell, i) {
1468 this.get_cells().map(function (cell, i) {
1470 if (cell instanceof codecell.CodeCell) {
1469 if (cell instanceof codecell.CodeCell) {
1471 cell.clear_output();
1470 cell.clear_output();
1472 }
1471 }
1473 });
1472 });
1474 this.set_dirty(true);
1473 this.set_dirty(true);
1475 };
1474 };
1476
1475
1477 /**
1476 /**
1478 * Scroll the selected CodeCell's output area.
1477 * Scroll the selected CodeCell's output area.
1479 *
1478 *
1480 * @method scroll_output
1479 * @method scroll_output
1481 * @param {Number} index A cell's numeric index
1480 * @param {Number} index A cell's numeric index
1482 */
1481 */
1483 Notebook.prototype.scroll_output = function (index) {
1482 Notebook.prototype.scroll_output = function (index) {
1484 var i = this.index_or_selected(index);
1483 var i = this.index_or_selected(index);
1485 var cell = this.get_cell(i);
1484 var cell = this.get_cell(i);
1486 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1485 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1487 cell.scroll_output();
1486 cell.scroll_output();
1488 this.set_dirty(true);
1487 this.set_dirty(true);
1489 }
1488 }
1490 };
1489 };
1491
1490
1492 /**
1491 /**
1493 * Expand each code cell's output area, and add a scrollbar for long output.
1492 * Expand each code cell's output area, and add a scrollbar for long output.
1494 *
1493 *
1495 * @method scroll_all_output
1494 * @method scroll_all_output
1496 */
1495 */
1497 Notebook.prototype.scroll_all_output = function () {
1496 Notebook.prototype.scroll_all_output = function () {
1498 this.get_cells().map(function (cell, i) {
1497 this.get_cells().map(function (cell, i) {
1499 if (cell instanceof codecell.CodeCell) {
1498 if (cell instanceof codecell.CodeCell) {
1500 cell.scroll_output();
1499 cell.scroll_output();
1501 }
1500 }
1502 });
1501 });
1503 // this should not be set if the `collapse` key is removed from nbformat
1502 // this should not be set if the `collapse` key is removed from nbformat
1504 this.set_dirty(true);
1503 this.set_dirty(true);
1505 };
1504 };
1506
1505
1507 /** Toggle whether a cell's output is collapsed or expanded.
1506 /** Toggle whether a cell's output is collapsed or expanded.
1508 *
1507 *
1509 * @method toggle_output
1508 * @method toggle_output
1510 * @param {Number} index A cell's numeric index
1509 * @param {Number} index A cell's numeric index
1511 */
1510 */
1512 Notebook.prototype.toggle_output = function (index) {
1511 Notebook.prototype.toggle_output = function (index) {
1513 var i = this.index_or_selected(index);
1512 var i = this.index_or_selected(index);
1514 var cell = this.get_cell(i);
1513 var cell = this.get_cell(i);
1515 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1514 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1516 cell.toggle_output();
1515 cell.toggle_output();
1517 this.set_dirty(true);
1516 this.set_dirty(true);
1518 }
1517 }
1519 };
1518 };
1520
1519
1521 /**
1520 /**
1522 * Hide/show the output of all cells.
1521 * Hide/show the output of all cells.
1523 *
1522 *
1524 * @method toggle_all_output
1523 * @method toggle_all_output
1525 */
1524 */
1526 Notebook.prototype.toggle_all_output = function () {
1525 Notebook.prototype.toggle_all_output = function () {
1527 this.get_cells().map(function (cell, i) {
1526 this.get_cells().map(function (cell, i) {
1528 if (cell instanceof codecell.CodeCell) {
1527 if (cell instanceof codecell.CodeCell) {
1529 cell.toggle_output();
1528 cell.toggle_output();
1530 }
1529 }
1531 });
1530 });
1532 // this should not be set if the `collapse` key is removed from nbformat
1531 // this should not be set if the `collapse` key is removed from nbformat
1533 this.set_dirty(true);
1532 this.set_dirty(true);
1534 };
1533 };
1535
1534
1536 /**
1535 /**
1537 * Toggle a scrollbar for long cell outputs.
1536 * Toggle a scrollbar for long cell outputs.
1538 *
1537 *
1539 * @method toggle_output_scroll
1538 * @method toggle_output_scroll
1540 * @param {Number} index A cell's numeric index
1539 * @param {Number} index A cell's numeric index
1541 */
1540 */
1542 Notebook.prototype.toggle_output_scroll = function (index) {
1541 Notebook.prototype.toggle_output_scroll = function (index) {
1543 var i = this.index_or_selected(index);
1542 var i = this.index_or_selected(index);
1544 var cell = this.get_cell(i);
1543 var cell = this.get_cell(i);
1545 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1544 if (cell !== null && (cell instanceof codecell.CodeCell)) {
1546 cell.toggle_output_scroll();
1545 cell.toggle_output_scroll();
1547 this.set_dirty(true);
1546 this.set_dirty(true);
1548 }
1547 }
1549 };
1548 };
1550
1549
1551 /**
1550 /**
1552 * Toggle the scrolling of long output on all cells.
1551 * Toggle the scrolling of long output on all cells.
1553 *
1552 *
1554 * @method toggle_all_output_scrolling
1553 * @method toggle_all_output_scrolling
1555 */
1554 */
1556 Notebook.prototype.toggle_all_output_scroll = function () {
1555 Notebook.prototype.toggle_all_output_scroll = function () {
1557 this.get_cells().map(function (cell, i) {
1556 this.get_cells().map(function (cell, i) {
1558 if (cell instanceof codecell.CodeCell) {
1557 if (cell instanceof codecell.CodeCell) {
1559 cell.toggle_output_scroll();
1558 cell.toggle_output_scroll();
1560 }
1559 }
1561 });
1560 });
1562 // this should not be set if the `collapse` key is removed from nbformat
1561 // this should not be set if the `collapse` key is removed from nbformat
1563 this.set_dirty(true);
1562 this.set_dirty(true);
1564 };
1563 };
1565
1564
1566 // Other cell functions: line numbers, ...
1565 // Other cell functions: line numbers, ...
1567
1566
1568 /**
1567 /**
1569 * Toggle line numbers in the selected cell's input area.
1568 * Toggle line numbers in the selected cell's input area.
1570 *
1569 *
1571 * @method cell_toggle_line_numbers
1570 * @method cell_toggle_line_numbers
1572 */
1571 */
1573 Notebook.prototype.cell_toggle_line_numbers = function() {
1572 Notebook.prototype.cell_toggle_line_numbers = function() {
1574 this.get_selected_cell().toggle_line_numbers();
1573 this.get_selected_cell().toggle_line_numbers();
1575 };
1574 };
1576
1575
1577 // Support for displaying input and output messages from other iPy clients.
1578
1579 /**
1580 * Toggles the ability to display input/output message events from
1581 * externally connected clients (i.e. other iPython shells, vim-ipython,
1582 * etc).
1583 *
1584 * @method toggle_ignore_unsolicited_msgs
1585 */
1586 Notebook.prototype.toggle_ignore_unsolicited_msgs = function () {
1587 this.include_other_output = !this.include_other_output;
1588 this.events.trigger('toggle_other_client_output.Notebook',
1589 [this.include_other_output]);
1590 return this.include_other_output;
1591 };
1592
1593 /**
1594 * Handles the display of unsolicited messages, i.e. inputs or outputs that
1595 * were generated by a client other than this notebook. New messages are
1596 * displayed at the bottom of the notebook.
1597 *
1598 * @method handle_unsolicited_msg
1599 */
1600 Notebook.prototype.handle_unsolicited_msg = function(msg) {
1601 if (!this.include_other_output) {
1602 return;
1603 }
1604 if (msg.msg_type == 'execute_input') {
1605 var cell = this.insert_cell_at_bottom('code');
1606 if (cell) {
1607 var cell_index = this.ncells() - 1;
1608 cell.last_msg_id = msg.parent_header.msg_id;
1609 cell.set_text(msg.content.code);
1610 cell._handle_execute_reply(msg);
1611 this.scroll_to_cell(cell_index);
1612 this.select(cell_index);
1613 }
1614 } else {
1615 /* Find the input cell that corresponds with the output, then add
1616 * the contents to the cell's output area.
1617 */
1618 var count = this.ncells();
1619 while (count--) {
1620 var cell = this.get_cell(count);
1621 if (cell && cell.last_msg_id == msg.parent_header.msg_id) {
1622 cell.output_area.handle_output(msg);
1623 this.scroll_to_cell(count);
1624 this.select(count);
1625 break;
1626 }
1627 }
1628 }
1629 };
1630
1631 /**
1576 /**
1632 * Set the codemirror mode for all code cells, including the default for
1577 * Set the codemirror mode for all code cells, including the default for
1633 * new code cells.
1578 * new code cells.
1634 *
1579 *
1635 * @method set_codemirror_mode
1580 * @method set_codemirror_mode
1636 */
1581 */
1637 Notebook.prototype.set_codemirror_mode = function(newmode){
1582 Notebook.prototype.set_codemirror_mode = function(newmode){
1638 if (newmode === this.codemirror_mode) {
1583 if (newmode === this.codemirror_mode) {
1639 return;
1584 return;
1640 }
1585 }
1641 this.codemirror_mode = newmode;
1586 this.codemirror_mode = newmode;
1642 codecell.CodeCell.options_default.cm_config.mode = newmode;
1587 codecell.CodeCell.options_default.cm_config.mode = newmode;
1643 var modename = newmode.mode || newmode.name || newmode;
1588 var modename = newmode.mode || newmode.name || newmode;
1644
1589
1645 var that = this;
1590 var that = this;
1646 utils.requireCodeMirrorMode(modename, function () {
1591 utils.requireCodeMirrorMode(modename, function () {
1647 that.get_cells().map(function(cell, i) {
1592 that.get_cells().map(function(cell, i) {
1648 if (cell.cell_type === 'code'){
1593 if (cell.cell_type === 'code'){
1649 cell.code_mirror.setOption('mode', newmode);
1594 cell.code_mirror.setOption('mode', newmode);
1650 // This is currently redundant, because cm_config ends up as
1595 // This is currently redundant, because cm_config ends up as
1651 // codemirror's own .options object, but I don't want to
1596 // codemirror's own .options object, but I don't want to
1652 // rely on that.
1597 // rely on that.
1653 cell.cm_config.mode = newmode;
1598 cell.cm_config.mode = newmode;
1654 }
1599 }
1655 });
1600 });
1656 });
1601 });
1657 };
1602 };
1658
1603
1659 // Session related things
1604 // Session related things
1660
1605
1661 /**
1606 /**
1662 * Start a new session and set it on each code cell.
1607 * Start a new session and set it on each code cell.
1663 *
1608 *
1664 * @method start_session
1609 * @method start_session
1665 */
1610 */
1666 Notebook.prototype.start_session = function (kernel_name) {
1611 Notebook.prototype.start_session = function (kernel_name) {
1667 if (this._session_starting) {
1612 if (this._session_starting) {
1668 throw new session.SessionAlreadyStarting();
1613 throw new session.SessionAlreadyStarting();
1669 }
1614 }
1670 this._session_starting = true;
1615 this._session_starting = true;
1671
1616
1672 var options = {
1617 var options = {
1673 base_url: this.base_url,
1618 base_url: this.base_url,
1674 ws_url: this.ws_url,
1619 ws_url: this.ws_url,
1675 notebook_path: this.notebook_path,
1620 notebook_path: this.notebook_path,
1676 notebook_name: this.notebook_name,
1621 notebook_name: this.notebook_name,
1677 kernel_name: kernel_name,
1622 kernel_name: kernel_name,
1678 notebook: this
1623 notebook: this
1679 };
1624 };
1680
1625
1681 var success = $.proxy(this._session_started, this);
1626 var success = $.proxy(this._session_started, this);
1682 var failure = $.proxy(this._session_start_failed, this);
1627 var failure = $.proxy(this._session_start_failed, this);
1683
1628
1684 if (this.session !== null) {
1629 if (this.session !== null) {
1685 this.session.restart(options, success, failure);
1630 this.session.restart(options, success, failure);
1686 } else {
1631 } else {
1687 this.session = new session.Session(options);
1632 this.session = new session.Session(options);
1688 this.session.start(success, failure);
1633 this.session.start(success, failure);
1689 }
1634 }
1690 };
1635 };
1691
1636
1692
1637
1693 /**
1638 /**
1694 * Once a session is started, link the code cells to the kernel and pass the
1639 * Once a session is started, link the code cells to the kernel and pass the
1695 * comm manager to the widget manager
1640 * comm manager to the widget manager
1696 *
1641 *
1697 */
1642 */
1698 Notebook.prototype._session_started = function (){
1643 Notebook.prototype._session_started = function (){
1699 this._session_starting = false;
1644 this._session_starting = false;
1700 this.kernel = this.session.kernel;
1645 this.kernel = this.session.kernel;
1701 var ncells = this.ncells();
1646 var ncells = this.ncells();
1702 for (var i=0; i<ncells; i++) {
1647 for (var i=0; i<ncells; i++) {
1703 var cell = this.get_cell(i);
1648 var cell = this.get_cell(i);
1704 if (cell instanceof codecell.CodeCell) {
1649 if (cell instanceof codecell.CodeCell) {
1705 cell.set_kernel(this.session.kernel);
1650 cell.set_kernel(this.session.kernel);
1706 }
1651 }
1707 }
1652 }
1708 this.kernel.unsolicited_msg_callback = $.proxy(this.handle_unsolicited_msg, this);
1709 };
1653 };
1710 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1654 Notebook.prototype._session_start_failed = function (jqxhr, status, error){
1711 this._session_starting = false;
1655 this._session_starting = false;
1712 utils.log_ajax_error(jqxhr, status, error);
1656 utils.log_ajax_error(jqxhr, status, error);
1713 };
1657 };
1714
1658
1715 /**
1659 /**
1716 * Prompt the user to restart the IPython kernel.
1660 * Prompt the user to restart the IPython kernel.
1717 *
1661 *
1718 * @method restart_kernel
1662 * @method restart_kernel
1719 */
1663 */
1720 Notebook.prototype.restart_kernel = function () {
1664 Notebook.prototype.restart_kernel = function () {
1721 var that = this;
1665 var that = this;
1722 dialog.modal({
1666 dialog.modal({
1723 notebook: this,
1667 notebook: this,
1724 keyboard_manager: this.keyboard_manager,
1668 keyboard_manager: this.keyboard_manager,
1725 title : "Restart kernel or continue running?",
1669 title : "Restart kernel or continue running?",
1726 body : $("<p/>").text(
1670 body : $("<p/>").text(
1727 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1671 'Do you want to restart the current kernel? You will lose all variables defined in it.'
1728 ),
1672 ),
1729 buttons : {
1673 buttons : {
1730 "Continue running" : {},
1674 "Continue running" : {},
1731 "Restart" : {
1675 "Restart" : {
1732 "class" : "btn-danger",
1676 "class" : "btn-danger",
1733 "click" : function() {
1677 "click" : function() {
1734 that.kernel.restart();
1678 that.kernel.restart();
1735 }
1679 }
1736 }
1680 }
1737 }
1681 }
1738 });
1682 });
1739 };
1683 };
1740
1684
1741 /**
1685 /**
1742 * Execute or render cell outputs and go into command mode.
1686 * Execute or render cell outputs and go into command mode.
1743 *
1687 *
1744 * @method execute_cell
1688 * @method execute_cell
1745 */
1689 */
1746 Notebook.prototype.execute_cell = function () {
1690 Notebook.prototype.execute_cell = function () {
1747 /**
1691 /**
1748 * mode = shift, ctrl, alt
1692 * mode = shift, ctrl, alt
1749 */
1693 */
1750 var cell = this.get_selected_cell();
1694 var cell = this.get_selected_cell();
1751
1695
1752 cell.execute();
1696 cell.execute();
1753 this.command_mode();
1697 this.command_mode();
1754 this.set_dirty(true);
1698 this.set_dirty(true);
1755 };
1699 };
1756
1700
1757 /**
1701 /**
1758 * Execute or render cell outputs and insert a new cell below.
1702 * Execute or render cell outputs and insert a new cell below.
1759 *
1703 *
1760 * @method execute_cell_and_insert_below
1704 * @method execute_cell_and_insert_below
1761 */
1705 */
1762 Notebook.prototype.execute_cell_and_insert_below = function () {
1706 Notebook.prototype.execute_cell_and_insert_below = function () {
1763 var cell = this.get_selected_cell();
1707 var cell = this.get_selected_cell();
1764 var cell_index = this.find_cell_index(cell);
1708 var cell_index = this.find_cell_index(cell);
1765
1709
1766 cell.execute();
1710 cell.execute();
1767
1711
1768 // If we are at the end always insert a new cell and return
1712 // If we are at the end always insert a new cell and return
1769 if (cell_index === (this.ncells()-1)) {
1713 if (cell_index === (this.ncells()-1)) {
1770 this.command_mode();
1714 this.command_mode();
1771 this.insert_cell_below();
1715 this.insert_cell_below();
1772 this.select(cell_index+1);
1716 this.select(cell_index+1);
1773 this.edit_mode();
1717 this.edit_mode();
1774 this.scroll_to_bottom();
1718 this.scroll_to_bottom();
1775 this.set_dirty(true);
1719 this.set_dirty(true);
1776 return;
1720 return;
1777 }
1721 }
1778
1722
1779 this.command_mode();
1723 this.command_mode();
1780 this.insert_cell_below();
1724 this.insert_cell_below();
1781 this.select(cell_index+1);
1725 this.select(cell_index+1);
1782 this.edit_mode();
1726 this.edit_mode();
1783 this.set_dirty(true);
1727 this.set_dirty(true);
1784 };
1728 };
1785
1729
1786 /**
1730 /**
1787 * Execute or render cell outputs and select the next cell.
1731 * Execute or render cell outputs and select the next cell.
1788 *
1732 *
1789 * @method execute_cell_and_select_below
1733 * @method execute_cell_and_select_below
1790 */
1734 */
1791 Notebook.prototype.execute_cell_and_select_below = function () {
1735 Notebook.prototype.execute_cell_and_select_below = function () {
1792
1736
1793 var cell = this.get_selected_cell();
1737 var cell = this.get_selected_cell();
1794 var cell_index = this.find_cell_index(cell);
1738 var cell_index = this.find_cell_index(cell);
1795
1739
1796 cell.execute();
1740 cell.execute();
1797
1741
1798 // If we are at the end always insert a new cell and return
1742 // If we are at the end always insert a new cell and return
1799 if (cell_index === (this.ncells()-1)) {
1743 if (cell_index === (this.ncells()-1)) {
1800 this.command_mode();
1744 this.command_mode();
1801 this.insert_cell_below();
1745 this.insert_cell_below();
1802 this.select(cell_index+1);
1746 this.select(cell_index+1);
1803 this.edit_mode();
1747 this.edit_mode();
1804 this.scroll_to_bottom();
1748 this.scroll_to_bottom();
1805 this.set_dirty(true);
1749 this.set_dirty(true);
1806 return;
1750 return;
1807 }
1751 }
1808
1752
1809 this.command_mode();
1753 this.command_mode();
1810 this.select(cell_index+1);
1754 this.select(cell_index+1);
1811 this.focus_cell();
1755 this.focus_cell();
1812 this.set_dirty(true);
1756 this.set_dirty(true);
1813 };
1757 };
1814
1758
1815 /**
1759 /**
1816 * Execute all cells below the selected cell.
1760 * Execute all cells below the selected cell.
1817 *
1761 *
1818 * @method execute_cells_below
1762 * @method execute_cells_below
1819 */
1763 */
1820 Notebook.prototype.execute_cells_below = function () {
1764 Notebook.prototype.execute_cells_below = function () {
1821 this.execute_cell_range(this.get_selected_index(), this.ncells());
1765 this.execute_cell_range(this.get_selected_index(), this.ncells());
1822 this.scroll_to_bottom();
1766 this.scroll_to_bottom();
1823 };
1767 };
1824
1768
1825 /**
1769 /**
1826 * Execute all cells above the selected cell.
1770 * Execute all cells above the selected cell.
1827 *
1771 *
1828 * @method execute_cells_above
1772 * @method execute_cells_above
1829 */
1773 */
1830 Notebook.prototype.execute_cells_above = function () {
1774 Notebook.prototype.execute_cells_above = function () {
1831 this.execute_cell_range(0, this.get_selected_index());
1775 this.execute_cell_range(0, this.get_selected_index());
1832 };
1776 };
1833
1777
1834 /**
1778 /**
1835 * Execute all cells.
1779 * Execute all cells.
1836 *
1780 *
1837 * @method execute_all_cells
1781 * @method execute_all_cells
1838 */
1782 */
1839 Notebook.prototype.execute_all_cells = function () {
1783 Notebook.prototype.execute_all_cells = function () {
1840 this.execute_cell_range(0, this.ncells());
1784 this.execute_cell_range(0, this.ncells());
1841 this.scroll_to_bottom();
1785 this.scroll_to_bottom();
1842 };
1786 };
1843
1787
1844 /**
1788 /**
1845 * Execute a contiguous range of cells.
1789 * Execute a contiguous range of cells.
1846 *
1790 *
1847 * @method execute_cell_range
1791 * @method execute_cell_range
1848 * @param {Number} start Index of the first cell to execute (inclusive)
1792 * @param {Number} start Index of the first cell to execute (inclusive)
1849 * @param {Number} end Index of the last cell to execute (exclusive)
1793 * @param {Number} end Index of the last cell to execute (exclusive)
1850 */
1794 */
1851 Notebook.prototype.execute_cell_range = function (start, end) {
1795 Notebook.prototype.execute_cell_range = function (start, end) {
1852 this.command_mode();
1796 this.command_mode();
1853 for (var i=start; i<end; i++) {
1797 for (var i=start; i<end; i++) {
1854 this.select(i);
1798 this.select(i);
1855 this.execute_cell();
1799 this.execute_cell();
1856 }
1800 }
1857 };
1801 };
1858
1802
1859 // Persistance and loading
1803 // Persistance and loading
1860
1804
1861 /**
1805 /**
1862 * Getter method for this notebook's name.
1806 * Getter method for this notebook's name.
1863 *
1807 *
1864 * @method get_notebook_name
1808 * @method get_notebook_name
1865 * @return {String} This notebook's name (excluding file extension)
1809 * @return {String} This notebook's name (excluding file extension)
1866 */
1810 */
1867 Notebook.prototype.get_notebook_name = function () {
1811 Notebook.prototype.get_notebook_name = function () {
1868 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1812 var nbname = this.notebook_name.substring(0,this.notebook_name.length-6);
1869 return nbname;
1813 return nbname;
1870 };
1814 };
1871
1815
1872 /**
1816 /**
1873 * Setter method for this notebook's name.
1817 * Setter method for this notebook's name.
1874 *
1818 *
1875 * @method set_notebook_name
1819 * @method set_notebook_name
1876 * @param {String} name A new name for this notebook
1820 * @param {String} name A new name for this notebook
1877 */
1821 */
1878 Notebook.prototype.set_notebook_name = function (name) {
1822 Notebook.prototype.set_notebook_name = function (name) {
1879 var parent = utils.url_path_split(this.notebook_path)[0];
1823 var parent = utils.url_path_split(this.notebook_path)[0];
1880 this.notebook_name = name;
1824 this.notebook_name = name;
1881 this.notebook_path = utils.url_path_join(parent, name);
1825 this.notebook_path = utils.url_path_join(parent, name);
1882 };
1826 };
1883
1827
1884 /**
1828 /**
1885 * Check that a notebook's name is valid.
1829 * Check that a notebook's name is valid.
1886 *
1830 *
1887 * @method test_notebook_name
1831 * @method test_notebook_name
1888 * @param {String} nbname A name for this notebook
1832 * @param {String} nbname A name for this notebook
1889 * @return {Boolean} True if the name is valid, false if invalid
1833 * @return {Boolean} True if the name is valid, false if invalid
1890 */
1834 */
1891 Notebook.prototype.test_notebook_name = function (nbname) {
1835 Notebook.prototype.test_notebook_name = function (nbname) {
1892 nbname = nbname || '';
1836 nbname = nbname || '';
1893 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1837 if (nbname.length>0 && !this.notebook_name_blacklist_re.test(nbname)) {
1894 return true;
1838 return true;
1895 } else {
1839 } else {
1896 return false;
1840 return false;
1897 }
1841 }
1898 };
1842 };
1899
1843
1900 /**
1844 /**
1901 * Load a notebook from JSON (.ipynb).
1845 * Load a notebook from JSON (.ipynb).
1902 *
1846 *
1903 * @method fromJSON
1847 * @method fromJSON
1904 * @param {Object} data JSON representation of a notebook
1848 * @param {Object} data JSON representation of a notebook
1905 */
1849 */
1906 Notebook.prototype.fromJSON = function (data) {
1850 Notebook.prototype.fromJSON = function (data) {
1907
1851
1908 var content = data.content;
1852 var content = data.content;
1909 var ncells = this.ncells();
1853 var ncells = this.ncells();
1910 var i;
1854 var i;
1911 for (i=0; i<ncells; i++) {
1855 for (i=0; i<ncells; i++) {
1912 // Always delete cell 0 as they get renumbered as they are deleted.
1856 // Always delete cell 0 as they get renumbered as they are deleted.
1913 this.delete_cell(0);
1857 this.delete_cell(0);
1914 }
1858 }
1915 // Save the metadata and name.
1859 // Save the metadata and name.
1916 this.metadata = content.metadata;
1860 this.metadata = content.metadata;
1917 this.notebook_name = data.name;
1861 this.notebook_name = data.name;
1918 this.notebook_path = data.path;
1862 this.notebook_path = data.path;
1919 var trusted = true;
1863 var trusted = true;
1920
1864
1921 // Trigger an event changing the kernel spec - this will set the default
1865 // Trigger an event changing the kernel spec - this will set the default
1922 // codemirror mode
1866 // codemirror mode
1923 if (this.metadata.kernelspec !== undefined) {
1867 if (this.metadata.kernelspec !== undefined) {
1924 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1868 this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
1925 }
1869 }
1926
1870
1927 // Set the codemirror mode from language_info metadata
1871 // Set the codemirror mode from language_info metadata
1928 if (this.metadata.language_info !== undefined) {
1872 if (this.metadata.language_info !== undefined) {
1929 var langinfo = this.metadata.language_info;
1873 var langinfo = this.metadata.language_info;
1930 // Mode 'null' should be plain, unhighlighted text.
1874 // Mode 'null' should be plain, unhighlighted text.
1931 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1875 var cm_mode = langinfo.codemirror_mode || langinfo.name || 'null';
1932 this.set_codemirror_mode(cm_mode);
1876 this.set_codemirror_mode(cm_mode);
1933 }
1877 }
1934
1878
1935 var new_cells = content.cells;
1879 var new_cells = content.cells;
1936 ncells = new_cells.length;
1880 ncells = new_cells.length;
1937 var cell_data = null;
1881 var cell_data = null;
1938 var new_cell = null;
1882 var new_cell = null;
1939 for (i=0; i<ncells; i++) {
1883 for (i=0; i<ncells; i++) {
1940 cell_data = new_cells[i];
1884 cell_data = new_cells[i];
1941 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1885 new_cell = this.insert_cell_at_index(cell_data.cell_type, i);
1942 new_cell.fromJSON(cell_data);
1886 new_cell.fromJSON(cell_data);
1943 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1887 if (new_cell.cell_type == 'code' && !new_cell.output_area.trusted) {
1944 trusted = false;
1888 trusted = false;
1945 }
1889 }
1946 }
1890 }
1947 if (trusted !== this.trusted) {
1891 if (trusted !== this.trusted) {
1948 this.trusted = trusted;
1892 this.trusted = trusted;
1949 this.events.trigger("trust_changed.Notebook", trusted);
1893 this.events.trigger("trust_changed.Notebook", trusted);
1950 }
1894 }
1951 };
1895 };
1952
1896
1953 /**
1897 /**
1954 * Dump this notebook into a JSON-friendly object.
1898 * Dump this notebook into a JSON-friendly object.
1955 *
1899 *
1956 * @method toJSON
1900 * @method toJSON
1957 * @return {Object} A JSON-friendly representation of this notebook.
1901 * @return {Object} A JSON-friendly representation of this notebook.
1958 */
1902 */
1959 Notebook.prototype.toJSON = function () {
1903 Notebook.prototype.toJSON = function () {
1960 /**
1904 /**
1961 * remove the conversion indicator, which only belongs in-memory
1905 * remove the conversion indicator, which only belongs in-memory
1962 */
1906 */
1963 delete this.metadata.orig_nbformat;
1907 delete this.metadata.orig_nbformat;
1964 delete this.metadata.orig_nbformat_minor;
1908 delete this.metadata.orig_nbformat_minor;
1965
1909
1966 var cells = this.get_cells();
1910 var cells = this.get_cells();
1967 var ncells = cells.length;
1911 var ncells = cells.length;
1968 var cell_array = new Array(ncells);
1912 var cell_array = new Array(ncells);
1969 var trusted = true;
1913 var trusted = true;
1970 for (var i=0; i<ncells; i++) {
1914 for (var i=0; i<ncells; i++) {
1971 var cell = cells[i];
1915 var cell = cells[i];
1972 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1916 if (cell.cell_type == 'code' && !cell.output_area.trusted) {
1973 trusted = false;
1917 trusted = false;
1974 }
1918 }
1975 cell_array[i] = cell.toJSON();
1919 cell_array[i] = cell.toJSON();
1976 }
1920 }
1977 var data = {
1921 var data = {
1978 cells: cell_array,
1922 cells: cell_array,
1979 metadata: this.metadata,
1923 metadata: this.metadata,
1980 nbformat: this.nbformat,
1924 nbformat: this.nbformat,
1981 nbformat_minor: this.nbformat_minor
1925 nbformat_minor: this.nbformat_minor
1982 };
1926 };
1983 if (trusted != this.trusted) {
1927 if (trusted != this.trusted) {
1984 this.trusted = trusted;
1928 this.trusted = trusted;
1985 this.events.trigger("trust_changed.Notebook", trusted);
1929 this.events.trigger("trust_changed.Notebook", trusted);
1986 }
1930 }
1987 return data;
1931 return data;
1988 };
1932 };
1989
1933
1990 /**
1934 /**
1991 * Start an autosave timer, for periodically saving the notebook.
1935 * Start an autosave timer, for periodically saving the notebook.
1992 *
1936 *
1993 * @method set_autosave_interval
1937 * @method set_autosave_interval
1994 * @param {Integer} interval the autosave interval in milliseconds
1938 * @param {Integer} interval the autosave interval in milliseconds
1995 */
1939 */
1996 Notebook.prototype.set_autosave_interval = function (interval) {
1940 Notebook.prototype.set_autosave_interval = function (interval) {
1997 var that = this;
1941 var that = this;
1998 // clear previous interval, so we don't get simultaneous timers
1942 // clear previous interval, so we don't get simultaneous timers
1999 if (this.autosave_timer) {
1943 if (this.autosave_timer) {
2000 clearInterval(this.autosave_timer);
1944 clearInterval(this.autosave_timer);
2001 }
1945 }
2002 if (!this.writable) {
1946 if (!this.writable) {
2003 // disable autosave if not writable
1947 // disable autosave if not writable
2004 interval = 0;
1948 interval = 0;
2005 }
1949 }
2006
1950
2007 this.autosave_interval = this.minimum_autosave_interval = interval;
1951 this.autosave_interval = this.minimum_autosave_interval = interval;
2008 if (interval) {
1952 if (interval) {
2009 this.autosave_timer = setInterval(function() {
1953 this.autosave_timer = setInterval(function() {
2010 if (that.dirty) {
1954 if (that.dirty) {
2011 that.save_notebook();
1955 that.save_notebook();
2012 }
1956 }
2013 }, interval);
1957 }, interval);
2014 this.events.trigger("autosave_enabled.Notebook", interval);
1958 this.events.trigger("autosave_enabled.Notebook", interval);
2015 } else {
1959 } else {
2016 this.autosave_timer = null;
1960 this.autosave_timer = null;
2017 this.events.trigger("autosave_disabled.Notebook");
1961 this.events.trigger("autosave_disabled.Notebook");
2018 }
1962 }
2019 };
1963 };
2020
1964
2021 /**
1965 /**
2022 * Save this notebook on the server. This becomes a notebook instance's
1966 * Save this notebook on the server. This becomes a notebook instance's
2023 * .save_notebook method *after* the entire notebook has been loaded.
1967 * .save_notebook method *after* the entire notebook has been loaded.
2024 *
1968 *
2025 * @method save_notebook
1969 * @method save_notebook
2026 */
1970 */
2027 Notebook.prototype.save_notebook = function () {
1971 Notebook.prototype.save_notebook = function () {
2028 if (!this._fully_loaded) {
1972 if (!this._fully_loaded) {
2029 this.events.trigger('notebook_save_failed.Notebook',
1973 this.events.trigger('notebook_save_failed.Notebook',
2030 new Error("Load failed, save is disabled")
1974 new Error("Load failed, save is disabled")
2031 );
1975 );
2032 return;
1976 return;
2033 } else if (!this.writable) {
1977 } else if (!this.writable) {
2034 this.events.trigger('notebook_save_failed.Notebook',
1978 this.events.trigger('notebook_save_failed.Notebook',
2035 new Error("Notebook is read-only")
1979 new Error("Notebook is read-only")
2036 );
1980 );
2037 return;
1981 return;
2038 }
1982 }
2039
1983
2040 // Create a JSON model to be sent to the server.
1984 // Create a JSON model to be sent to the server.
2041 var model = {
1985 var model = {
2042 type : "notebook",
1986 type : "notebook",
2043 content : this.toJSON()
1987 content : this.toJSON()
2044 };
1988 };
2045 // time the ajax call for autosave tuning purposes.
1989 // time the ajax call for autosave tuning purposes.
2046 var start = new Date().getTime();
1990 var start = new Date().getTime();
2047
1991
2048 var that = this;
1992 var that = this;
2049 return this.contents.save(this.notebook_path, model).then(
1993 return this.contents.save(this.notebook_path, model).then(
2050 $.proxy(this.save_notebook_success, this, start),
1994 $.proxy(this.save_notebook_success, this, start),
2051 function (error) {
1995 function (error) {
2052 that.events.trigger('notebook_save_failed.Notebook', error);
1996 that.events.trigger('notebook_save_failed.Notebook', error);
2053 }
1997 }
2054 );
1998 );
2055 };
1999 };
2056
2000
2057 /**
2001 /**
2058 * Success callback for saving a notebook.
2002 * Success callback for saving a notebook.
2059 *
2003 *
2060 * @method save_notebook_success
2004 * @method save_notebook_success
2061 * @param {Integer} start Time when the save request start
2005 * @param {Integer} start Time when the save request start
2062 * @param {Object} data JSON representation of a notebook
2006 * @param {Object} data JSON representation of a notebook
2063 */
2007 */
2064 Notebook.prototype.save_notebook_success = function (start, data) {
2008 Notebook.prototype.save_notebook_success = function (start, data) {
2065 this.set_dirty(false);
2009 this.set_dirty(false);
2066 if (data.message) {
2010 if (data.message) {
2067 // save succeeded, but validation failed.
2011 // save succeeded, but validation failed.
2068 var body = $("<div>");
2012 var body = $("<div>");
2069 var title = "Notebook validation failed";
2013 var title = "Notebook validation failed";
2070
2014
2071 body.append($("<p>").text(
2015 body.append($("<p>").text(
2072 "The save operation succeeded," +
2016 "The save operation succeeded," +
2073 " but the notebook does not appear to be valid." +
2017 " but the notebook does not appear to be valid." +
2074 " The validation error was:"
2018 " The validation error was:"
2075 )).append($("<div>").addClass("validation-error").append(
2019 )).append($("<div>").addClass("validation-error").append(
2076 $("<pre>").text(data.message)
2020 $("<pre>").text(data.message)
2077 ));
2021 ));
2078 dialog.modal({
2022 dialog.modal({
2079 notebook: this,
2023 notebook: this,
2080 keyboard_manager: this.keyboard_manager,
2024 keyboard_manager: this.keyboard_manager,
2081 title: title,
2025 title: title,
2082 body: body,
2026 body: body,
2083 buttons : {
2027 buttons : {
2084 OK : {
2028 OK : {
2085 "class" : "btn-primary"
2029 "class" : "btn-primary"
2086 }
2030 }
2087 }
2031 }
2088 });
2032 });
2089 }
2033 }
2090 this.events.trigger('notebook_saved.Notebook');
2034 this.events.trigger('notebook_saved.Notebook');
2091 this._update_autosave_interval(start);
2035 this._update_autosave_interval(start);
2092 if (this._checkpoint_after_save) {
2036 if (this._checkpoint_after_save) {
2093 this.create_checkpoint();
2037 this.create_checkpoint();
2094 this._checkpoint_after_save = false;
2038 this._checkpoint_after_save = false;
2095 }
2039 }
2096 };
2040 };
2097
2041
2098 /**
2042 /**
2099 * update the autosave interval based on how long the last save took
2043 * update the autosave interval based on how long the last save took
2100 *
2044 *
2101 * @method _update_autosave_interval
2045 * @method _update_autosave_interval
2102 * @param {Integer} timestamp when the save request started
2046 * @param {Integer} timestamp when the save request started
2103 */
2047 */
2104 Notebook.prototype._update_autosave_interval = function (start) {
2048 Notebook.prototype._update_autosave_interval = function (start) {
2105 var duration = (new Date().getTime() - start);
2049 var duration = (new Date().getTime() - start);
2106 if (this.autosave_interval) {
2050 if (this.autosave_interval) {
2107 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2051 // new save interval: higher of 10x save duration or parameter (default 30 seconds)
2108 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2052 var interval = Math.max(10 * duration, this.minimum_autosave_interval);
2109 // round to 10 seconds, otherwise we will be setting a new interval too often
2053 // round to 10 seconds, otherwise we will be setting a new interval too often
2110 interval = 10000 * Math.round(interval / 10000);
2054 interval = 10000 * Math.round(interval / 10000);
2111 // set new interval, if it's changed
2055 // set new interval, if it's changed
2112 if (interval != this.autosave_interval) {
2056 if (interval != this.autosave_interval) {
2113 this.set_autosave_interval(interval);
2057 this.set_autosave_interval(interval);
2114 }
2058 }
2115 }
2059 }
2116 };
2060 };
2117
2061
2118 /**
2062 /**
2119 * Explicitly trust the output of this notebook.
2063 * Explicitly trust the output of this notebook.
2120 *
2064 *
2121 * @method trust_notebook
2065 * @method trust_notebook
2122 */
2066 */
2123 Notebook.prototype.trust_notebook = function () {
2067 Notebook.prototype.trust_notebook = function () {
2124 var body = $("<div>").append($("<p>")
2068 var body = $("<div>").append($("<p>")
2125 .text("A trusted IPython notebook may execute hidden malicious code ")
2069 .text("A trusted IPython notebook may execute hidden malicious code ")
2126 .append($("<strong>")
2070 .append($("<strong>")
2127 .append(
2071 .append(
2128 $("<em>").text("when you open it")
2072 $("<em>").text("when you open it")
2129 )
2073 )
2130 ).append(".").append(
2074 ).append(".").append(
2131 " Selecting trust will immediately reload this notebook in a trusted state."
2075 " Selecting trust will immediately reload this notebook in a trusted state."
2132 ).append(
2076 ).append(
2133 " For more information, see the "
2077 " For more information, see the "
2134 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2078 ).append($("<a>").attr("href", "http://ipython.org/ipython-doc/2/notebook/security.html")
2135 .text("IPython security documentation")
2079 .text("IPython security documentation")
2136 ).append(".")
2080 ).append(".")
2137 );
2081 );
2138
2082
2139 var nb = this;
2083 var nb = this;
2140 dialog.modal({
2084 dialog.modal({
2141 notebook: this,
2085 notebook: this,
2142 keyboard_manager: this.keyboard_manager,
2086 keyboard_manager: this.keyboard_manager,
2143 title: "Trust this notebook?",
2087 title: "Trust this notebook?",
2144 body: body,
2088 body: body,
2145
2089
2146 buttons: {
2090 buttons: {
2147 Cancel : {},
2091 Cancel : {},
2148 Trust : {
2092 Trust : {
2149 class : "btn-danger",
2093 class : "btn-danger",
2150 click : function () {
2094 click : function () {
2151 var cells = nb.get_cells();
2095 var cells = nb.get_cells();
2152 for (var i = 0; i < cells.length; i++) {
2096 for (var i = 0; i < cells.length; i++) {
2153 var cell = cells[i];
2097 var cell = cells[i];
2154 if (cell.cell_type == 'code') {
2098 if (cell.cell_type == 'code') {
2155 cell.output_area.trusted = true;
2099 cell.output_area.trusted = true;
2156 }
2100 }
2157 }
2101 }
2158 nb.events.on('notebook_saved.Notebook', function () {
2102 nb.events.on('notebook_saved.Notebook', function () {
2159 window.location.reload();
2103 window.location.reload();
2160 });
2104 });
2161 nb.save_notebook();
2105 nb.save_notebook();
2162 }
2106 }
2163 }
2107 }
2164 }
2108 }
2165 });
2109 });
2166 };
2110 };
2167
2111
2168 Notebook.prototype.copy_notebook = function () {
2112 Notebook.prototype.copy_notebook = function () {
2169 var that = this;
2113 var that = this;
2170 var base_url = this.base_url;
2114 var base_url = this.base_url;
2171 var w = window.open();
2115 var w = window.open();
2172 var parent = utils.url_path_split(this.notebook_path)[0];
2116 var parent = utils.url_path_split(this.notebook_path)[0];
2173 this.contents.copy(this.notebook_path, parent).then(
2117 this.contents.copy(this.notebook_path, parent).then(
2174 function (data) {
2118 function (data) {
2175 w.location = utils.url_join_encode(
2119 w.location = utils.url_join_encode(
2176 base_url, 'notebooks', data.path
2120 base_url, 'notebooks', data.path
2177 );
2121 );
2178 },
2122 },
2179 function(error) {
2123 function(error) {
2180 w.close();
2124 w.close();
2181 that.events.trigger('notebook_copy_failed', error);
2125 that.events.trigger('notebook_copy_failed', error);
2182 }
2126 }
2183 );
2127 );
2184 };
2128 };
2185
2129
2186 Notebook.prototype.rename = function (new_name) {
2130 Notebook.prototype.rename = function (new_name) {
2187 if (!new_name.match(/\.ipynb$/)) {
2131 if (!new_name.match(/\.ipynb$/)) {
2188 new_name = new_name + ".ipynb";
2132 new_name = new_name + ".ipynb";
2189 }
2133 }
2190
2134
2191 var that = this;
2135 var that = this;
2192 var parent = utils.url_path_split(this.notebook_path)[0];
2136 var parent = utils.url_path_split(this.notebook_path)[0];
2193 var new_path = utils.url_path_join(parent, new_name);
2137 var new_path = utils.url_path_join(parent, new_name);
2194 return this.contents.rename(this.notebook_path, new_path).then(
2138 return this.contents.rename(this.notebook_path, new_path).then(
2195 function (json) {
2139 function (json) {
2196 that.notebook_name = json.name;
2140 that.notebook_name = json.name;
2197 that.notebook_path = json.path;
2141 that.notebook_path = json.path;
2198 that.session.rename_notebook(json.path);
2142 that.session.rename_notebook(json.path);
2199 that.events.trigger('notebook_renamed.Notebook', json);
2143 that.events.trigger('notebook_renamed.Notebook', json);
2200 }
2144 }
2201 );
2145 );
2202 };
2146 };
2203
2147
2204 Notebook.prototype.delete = function () {
2148 Notebook.prototype.delete = function () {
2205 this.contents.delete(this.notebook_path);
2149 this.contents.delete(this.notebook_path);
2206 };
2150 };
2207
2151
2208 /**
2152 /**
2209 * Request a notebook's data from the server.
2153 * Request a notebook's data from the server.
2210 *
2154 *
2211 * @method load_notebook
2155 * @method load_notebook
2212 * @param {String} notebook_path A notebook to load
2156 * @param {String} notebook_path A notebook to load
2213 */
2157 */
2214 Notebook.prototype.load_notebook = function (notebook_path) {
2158 Notebook.prototype.load_notebook = function (notebook_path) {
2215 this.notebook_path = notebook_path;
2159 this.notebook_path = notebook_path;
2216 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2160 this.notebook_name = utils.url_path_split(this.notebook_path)[1];
2217 this.events.trigger('notebook_loading.Notebook');
2161 this.events.trigger('notebook_loading.Notebook');
2218 this.contents.get(notebook_path, {type: 'notebook'}).then(
2162 this.contents.get(notebook_path, {type: 'notebook'}).then(
2219 $.proxy(this.load_notebook_success, this),
2163 $.proxy(this.load_notebook_success, this),
2220 $.proxy(this.load_notebook_error, this)
2164 $.proxy(this.load_notebook_error, this)
2221 );
2165 );
2222 };
2166 };
2223
2167
2224 /**
2168 /**
2225 * Success callback for loading a notebook from the server.
2169 * Success callback for loading a notebook from the server.
2226 *
2170 *
2227 * Load notebook data from the JSON response.
2171 * Load notebook data from the JSON response.
2228 *
2172 *
2229 * @method load_notebook_success
2173 * @method load_notebook_success
2230 * @param {Object} data JSON representation of a notebook
2174 * @param {Object} data JSON representation of a notebook
2231 */
2175 */
2232 Notebook.prototype.load_notebook_success = function (data) {
2176 Notebook.prototype.load_notebook_success = function (data) {
2233 var failed, msg;
2177 var failed, msg;
2234 try {
2178 try {
2235 this.fromJSON(data);
2179 this.fromJSON(data);
2236 } catch (e) {
2180 } catch (e) {
2237 failed = e;
2181 failed = e;
2238 console.log("Notebook failed to load from JSON:", e);
2182 console.log("Notebook failed to load from JSON:", e);
2239 }
2183 }
2240 if (failed || data.message) {
2184 if (failed || data.message) {
2241 // *either* fromJSON failed or validation failed
2185 // *either* fromJSON failed or validation failed
2242 var body = $("<div>");
2186 var body = $("<div>");
2243 var title;
2187 var title;
2244 if (failed) {
2188 if (failed) {
2245 title = "Notebook failed to load";
2189 title = "Notebook failed to load";
2246 body.append($("<p>").text(
2190 body.append($("<p>").text(
2247 "The error was: "
2191 "The error was: "
2248 )).append($("<div>").addClass("js-error").text(
2192 )).append($("<div>").addClass("js-error").text(
2249 failed.toString()
2193 failed.toString()
2250 )).append($("<p>").text(
2194 )).append($("<p>").text(
2251 "See the error console for details."
2195 "See the error console for details."
2252 ));
2196 ));
2253 } else {
2197 } else {
2254 title = "Notebook validation failed";
2198 title = "Notebook validation failed";
2255 }
2199 }
2256
2200
2257 if (data.message) {
2201 if (data.message) {
2258 if (failed) {
2202 if (failed) {
2259 msg = "The notebook also failed validation:";
2203 msg = "The notebook also failed validation:";
2260 } else {
2204 } else {
2261 msg = "An invalid notebook may not function properly." +
2205 msg = "An invalid notebook may not function properly." +
2262 " The validation error was:";
2206 " The validation error was:";
2263 }
2207 }
2264 body.append($("<p>").text(
2208 body.append($("<p>").text(
2265 msg
2209 msg
2266 )).append($("<div>").addClass("validation-error").append(
2210 )).append($("<div>").addClass("validation-error").append(
2267 $("<pre>").text(data.message)
2211 $("<pre>").text(data.message)
2268 ));
2212 ));
2269 }
2213 }
2270
2214
2271 dialog.modal({
2215 dialog.modal({
2272 notebook: this,
2216 notebook: this,
2273 keyboard_manager: this.keyboard_manager,
2217 keyboard_manager: this.keyboard_manager,
2274 title: title,
2218 title: title,
2275 body: body,
2219 body: body,
2276 buttons : {
2220 buttons : {
2277 OK : {
2221 OK : {
2278 "class" : "btn-primary"
2222 "class" : "btn-primary"
2279 }
2223 }
2280 }
2224 }
2281 });
2225 });
2282 }
2226 }
2283 if (this.ncells() === 0) {
2227 if (this.ncells() === 0) {
2284 this.insert_cell_below('code');
2228 this.insert_cell_below('code');
2285 this.edit_mode(0);
2229 this.edit_mode(0);
2286 } else {
2230 } else {
2287 this.select(0);
2231 this.select(0);
2288 this.handle_command_mode(this.get_cell(0));
2232 this.handle_command_mode(this.get_cell(0));
2289 }
2233 }
2290 this.set_dirty(false);
2234 this.set_dirty(false);
2291 this.scroll_to_top();
2235 this.scroll_to_top();
2292 this.writable = data.writable || false;
2236 this.writable = data.writable || false;
2293 var nbmodel = data.content;
2237 var nbmodel = data.content;
2294 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2238 var orig_nbformat = nbmodel.metadata.orig_nbformat;
2295 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2239 var orig_nbformat_minor = nbmodel.metadata.orig_nbformat_minor;
2296 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2240 if (orig_nbformat !== undefined && nbmodel.nbformat !== orig_nbformat) {
2297 var src;
2241 var src;
2298 if (nbmodel.nbformat > orig_nbformat) {
2242 if (nbmodel.nbformat > orig_nbformat) {
2299 src = " an older notebook format ";
2243 src = " an older notebook format ";
2300 } else {
2244 } else {
2301 src = " a newer notebook format ";
2245 src = " a newer notebook format ";
2302 }
2246 }
2303
2247
2304 msg = "This notebook has been converted from" + src +
2248 msg = "This notebook has been converted from" + src +
2305 "(v"+orig_nbformat+") to the current notebook " +
2249 "(v"+orig_nbformat+") to the current notebook " +
2306 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2250 "format (v"+nbmodel.nbformat+"). The next time you save this notebook, the " +
2307 "current notebook format will be used.";
2251 "current notebook format will be used.";
2308
2252
2309 if (nbmodel.nbformat > orig_nbformat) {
2253 if (nbmodel.nbformat > orig_nbformat) {
2310 msg += " Older versions of IPython may not be able to read the new format.";
2254 msg += " Older versions of IPython may not be able to read the new format.";
2311 } else {
2255 } else {
2312 msg += " Some features of the original notebook may not be available.";
2256 msg += " Some features of the original notebook may not be available.";
2313 }
2257 }
2314 msg += " To preserve the original version, close the " +
2258 msg += " To preserve the original version, close the " +
2315 "notebook without saving it.";
2259 "notebook without saving it.";
2316 dialog.modal({
2260 dialog.modal({
2317 notebook: this,
2261 notebook: this,
2318 keyboard_manager: this.keyboard_manager,
2262 keyboard_manager: this.keyboard_manager,
2319 title : "Notebook converted",
2263 title : "Notebook converted",
2320 body : msg,
2264 body : msg,
2321 buttons : {
2265 buttons : {
2322 OK : {
2266 OK : {
2323 class : "btn-primary"
2267 class : "btn-primary"
2324 }
2268 }
2325 }
2269 }
2326 });
2270 });
2327 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2271 } else if (this.nbformat_minor < nbmodel.nbformat_minor) {
2328 this.nbformat_minor = nbmodel.nbformat_minor;
2272 this.nbformat_minor = nbmodel.nbformat_minor;
2329 }
2273 }
2330
2274
2331 // Create the session after the notebook is completely loaded to prevent
2275 // Create the session after the notebook is completely loaded to prevent
2332 // code execution upon loading, which is a security risk.
2276 // code execution upon loading, which is a security risk.
2333 if (this.session === null) {
2277 if (this.session === null) {
2334 var kernel_name;
2278 var kernel_name;
2335 if (this.metadata.kernelspec) {
2279 if (this.metadata.kernelspec) {
2336 var kernelspec = this.metadata.kernelspec || {};
2280 var kernelspec = this.metadata.kernelspec || {};
2337 kernel_name = kernelspec.name;
2281 kernel_name = kernelspec.name;
2338 } else {
2282 } else {
2339 kernel_name = utils.get_url_param('kernel_name');
2283 kernel_name = utils.get_url_param('kernel_name');
2340 }
2284 }
2341 this.start_session(kernel_name);
2285 this.start_session(kernel_name);
2342 }
2286 }
2343 // load our checkpoint list
2287 // load our checkpoint list
2344 this.list_checkpoints();
2288 this.list_checkpoints();
2345
2289
2346 // load toolbar state
2290 // load toolbar state
2347 if (this.metadata.celltoolbar) {
2291 if (this.metadata.celltoolbar) {
2348 celltoolbar.CellToolbar.global_show();
2292 celltoolbar.CellToolbar.global_show();
2349 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2293 celltoolbar.CellToolbar.activate_preset(this.metadata.celltoolbar);
2350 } else {
2294 } else {
2351 celltoolbar.CellToolbar.global_hide();
2295 celltoolbar.CellToolbar.global_hide();
2352 }
2296 }
2353
2297
2354 if (!this.writable) {
2298 if (!this.writable) {
2355 this.set_autosave_interval(0);
2299 this.set_autosave_interval(0);
2356 this.events.trigger('notebook_read_only.Notebook');
2300 this.events.trigger('notebook_read_only.Notebook');
2357 }
2301 }
2358
2302
2359 // now that we're fully loaded, it is safe to restore save functionality
2303 // now that we're fully loaded, it is safe to restore save functionality
2360 this._fully_loaded = true;
2304 this._fully_loaded = true;
2361 this.events.trigger('notebook_loaded.Notebook');
2305 this.events.trigger('notebook_loaded.Notebook');
2362 };
2306 };
2363
2307
2364 /**
2308 /**
2365 * Failure callback for loading a notebook from the server.
2309 * Failure callback for loading a notebook from the server.
2366 *
2310 *
2367 * @method load_notebook_error
2311 * @method load_notebook_error
2368 * @param {Error} error
2312 * @param {Error} error
2369 */
2313 */
2370 Notebook.prototype.load_notebook_error = function (error) {
2314 Notebook.prototype.load_notebook_error = function (error) {
2371 this.events.trigger('notebook_load_failed.Notebook', error);
2315 this.events.trigger('notebook_load_failed.Notebook', error);
2372 var msg;
2316 var msg;
2373 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2317 if (error.name === utils.XHR_ERROR && error.xhr.status === 500) {
2374 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2318 utils.log_ajax_error(error.xhr, error.xhr_status, error.xhr_error);
2375 msg = "An unknown error occurred while loading this notebook. " +
2319 msg = "An unknown error occurred while loading this notebook. " +
2376 "This version can load notebook formats " +
2320 "This version can load notebook formats " +
2377 "v" + this.nbformat + " or earlier. See the server log for details.";
2321 "v" + this.nbformat + " or earlier. See the server log for details.";
2378 } else {
2322 } else {
2379 msg = error.message;
2323 msg = error.message;
2380 }
2324 }
2381 dialog.modal({
2325 dialog.modal({
2382 notebook: this,
2326 notebook: this,
2383 keyboard_manager: this.keyboard_manager,
2327 keyboard_manager: this.keyboard_manager,
2384 title: "Error loading notebook",
2328 title: "Error loading notebook",
2385 body : msg,
2329 body : msg,
2386 buttons : {
2330 buttons : {
2387 "OK": {}
2331 "OK": {}
2388 }
2332 }
2389 });
2333 });
2390 };
2334 };
2391
2335
2392 /********************* checkpoint-related *********************/
2336 /********************* checkpoint-related *********************/
2393
2337
2394 /**
2338 /**
2395 * Save the notebook then immediately create a checkpoint.
2339 * Save the notebook then immediately create a checkpoint.
2396 *
2340 *
2397 * @method save_checkpoint
2341 * @method save_checkpoint
2398 */
2342 */
2399 Notebook.prototype.save_checkpoint = function () {
2343 Notebook.prototype.save_checkpoint = function () {
2400 this._checkpoint_after_save = true;
2344 this._checkpoint_after_save = true;
2401 this.save_notebook();
2345 this.save_notebook();
2402 };
2346 };
2403
2347
2404 /**
2348 /**
2405 * Add a checkpoint for this notebook.
2349 * Add a checkpoint for this notebook.
2406 * for use as a callback from checkpoint creation.
2350 * for use as a callback from checkpoint creation.
2407 *
2351 *
2408 * @method add_checkpoint
2352 * @method add_checkpoint
2409 */
2353 */
2410 Notebook.prototype.add_checkpoint = function (checkpoint) {
2354 Notebook.prototype.add_checkpoint = function (checkpoint) {
2411 var found = false;
2355 var found = false;
2412 for (var i = 0; i < this.checkpoints.length; i++) {
2356 for (var i = 0; i < this.checkpoints.length; i++) {
2413 var existing = this.checkpoints[i];
2357 var existing = this.checkpoints[i];
2414 if (existing.id == checkpoint.id) {
2358 if (existing.id == checkpoint.id) {
2415 found = true;
2359 found = true;
2416 this.checkpoints[i] = checkpoint;
2360 this.checkpoints[i] = checkpoint;
2417 break;
2361 break;
2418 }
2362 }
2419 }
2363 }
2420 if (!found) {
2364 if (!found) {
2421 this.checkpoints.push(checkpoint);
2365 this.checkpoints.push(checkpoint);
2422 }
2366 }
2423 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2367 this.last_checkpoint = this.checkpoints[this.checkpoints.length - 1];
2424 };
2368 };
2425
2369
2426 /**
2370 /**
2427 * List checkpoints for this notebook.
2371 * List checkpoints for this notebook.
2428 *
2372 *
2429 * @method list_checkpoints
2373 * @method list_checkpoints
2430 */
2374 */
2431 Notebook.prototype.list_checkpoints = function () {
2375 Notebook.prototype.list_checkpoints = function () {
2432 var that = this;
2376 var that = this;
2433 this.contents.list_checkpoints(this.notebook_path).then(
2377 this.contents.list_checkpoints(this.notebook_path).then(
2434 $.proxy(this.list_checkpoints_success, this),
2378 $.proxy(this.list_checkpoints_success, this),
2435 function(error) {
2379 function(error) {
2436 that.events.trigger('list_checkpoints_failed.Notebook', error);
2380 that.events.trigger('list_checkpoints_failed.Notebook', error);
2437 }
2381 }
2438 );
2382 );
2439 };
2383 };
2440
2384
2441 /**
2385 /**
2442 * Success callback for listing checkpoints.
2386 * Success callback for listing checkpoints.
2443 *
2387 *
2444 * @method list_checkpoint_success
2388 * @method list_checkpoint_success
2445 * @param {Object} data JSON representation of a checkpoint
2389 * @param {Object} data JSON representation of a checkpoint
2446 */
2390 */
2447 Notebook.prototype.list_checkpoints_success = function (data) {
2391 Notebook.prototype.list_checkpoints_success = function (data) {
2448 this.checkpoints = data;
2392 this.checkpoints = data;
2449 if (data.length) {
2393 if (data.length) {
2450 this.last_checkpoint = data[data.length - 1];
2394 this.last_checkpoint = data[data.length - 1];
2451 } else {
2395 } else {
2452 this.last_checkpoint = null;
2396 this.last_checkpoint = null;
2453 }
2397 }
2454 this.events.trigger('checkpoints_listed.Notebook', [data]);
2398 this.events.trigger('checkpoints_listed.Notebook', [data]);
2455 };
2399 };
2456
2400
2457 /**
2401 /**
2458 * Create a checkpoint of this notebook on the server from the most recent save.
2402 * Create a checkpoint of this notebook on the server from the most recent save.
2459 *
2403 *
2460 * @method create_checkpoint
2404 * @method create_checkpoint
2461 */
2405 */
2462 Notebook.prototype.create_checkpoint = function () {
2406 Notebook.prototype.create_checkpoint = function () {
2463 var that = this;
2407 var that = this;
2464 this.contents.create_checkpoint(this.notebook_path).then(
2408 this.contents.create_checkpoint(this.notebook_path).then(
2465 $.proxy(this.create_checkpoint_success, this),
2409 $.proxy(this.create_checkpoint_success, this),
2466 function (error) {
2410 function (error) {
2467 that.events.trigger('checkpoint_failed.Notebook', error);
2411 that.events.trigger('checkpoint_failed.Notebook', error);
2468 }
2412 }
2469 );
2413 );
2470 };
2414 };
2471
2415
2472 /**
2416 /**
2473 * Success callback for creating a checkpoint.
2417 * Success callback for creating a checkpoint.
2474 *
2418 *
2475 * @method create_checkpoint_success
2419 * @method create_checkpoint_success
2476 * @param {Object} data JSON representation of a checkpoint
2420 * @param {Object} data JSON representation of a checkpoint
2477 */
2421 */
2478 Notebook.prototype.create_checkpoint_success = function (data) {
2422 Notebook.prototype.create_checkpoint_success = function (data) {
2479 this.add_checkpoint(data);
2423 this.add_checkpoint(data);
2480 this.events.trigger('checkpoint_created.Notebook', data);
2424 this.events.trigger('checkpoint_created.Notebook', data);
2481 };
2425 };
2482
2426
2483 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2427 Notebook.prototype.restore_checkpoint_dialog = function (checkpoint) {
2484 var that = this;
2428 var that = this;
2485 checkpoint = checkpoint || this.last_checkpoint;
2429 checkpoint = checkpoint || this.last_checkpoint;
2486 if ( ! checkpoint ) {
2430 if ( ! checkpoint ) {
2487 console.log("restore dialog, but no checkpoint to restore to!");
2431 console.log("restore dialog, but no checkpoint to restore to!");
2488 return;
2432 return;
2489 }
2433 }
2490 var body = $('<div/>').append(
2434 var body = $('<div/>').append(
2491 $('<p/>').addClass("p-space").text(
2435 $('<p/>').addClass("p-space").text(
2492 "Are you sure you want to revert the notebook to " +
2436 "Are you sure you want to revert the notebook to " +
2493 "the latest checkpoint?"
2437 "the latest checkpoint?"
2494 ).append(
2438 ).append(
2495 $("<strong/>").text(
2439 $("<strong/>").text(
2496 " This cannot be undone."
2440 " This cannot be undone."
2497 )
2441 )
2498 )
2442 )
2499 ).append(
2443 ).append(
2500 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2444 $('<p/>').addClass("p-space").text("The checkpoint was last updated at:")
2501 ).append(
2445 ).append(
2502 $('<p/>').addClass("p-space").text(
2446 $('<p/>').addClass("p-space").text(
2503 Date(checkpoint.last_modified)
2447 Date(checkpoint.last_modified)
2504 ).css("text-align", "center")
2448 ).css("text-align", "center")
2505 );
2449 );
2506
2450
2507 dialog.modal({
2451 dialog.modal({
2508 notebook: this,
2452 notebook: this,
2509 keyboard_manager: this.keyboard_manager,
2453 keyboard_manager: this.keyboard_manager,
2510 title : "Revert notebook to checkpoint",
2454 title : "Revert notebook to checkpoint",
2511 body : body,
2455 body : body,
2512 buttons : {
2456 buttons : {
2513 Revert : {
2457 Revert : {
2514 class : "btn-danger",
2458 class : "btn-danger",
2515 click : function () {
2459 click : function () {
2516 that.restore_checkpoint(checkpoint.id);
2460 that.restore_checkpoint(checkpoint.id);
2517 }
2461 }
2518 },
2462 },
2519 Cancel : {}
2463 Cancel : {}
2520 }
2464 }
2521 });
2465 });
2522 };
2466 };
2523
2467
2524 /**
2468 /**
2525 * Restore the notebook to a checkpoint state.
2469 * Restore the notebook to a checkpoint state.
2526 *
2470 *
2527 * @method restore_checkpoint
2471 * @method restore_checkpoint
2528 * @param {String} checkpoint ID
2472 * @param {String} checkpoint ID
2529 */
2473 */
2530 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2474 Notebook.prototype.restore_checkpoint = function (checkpoint) {
2531 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2475 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2532 var that = this;
2476 var that = this;
2533 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2477 this.contents.restore_checkpoint(this.notebook_path, checkpoint).then(
2534 $.proxy(this.restore_checkpoint_success, this),
2478 $.proxy(this.restore_checkpoint_success, this),
2535 function (error) {
2479 function (error) {
2536 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2480 that.events.trigger('checkpoint_restore_failed.Notebook', error);
2537 }
2481 }
2538 );
2482 );
2539 };
2483 };
2540
2484
2541 /**
2485 /**
2542 * Success callback for restoring a notebook to a checkpoint.
2486 * Success callback for restoring a notebook to a checkpoint.
2543 *
2487 *
2544 * @method restore_checkpoint_success
2488 * @method restore_checkpoint_success
2545 */
2489 */
2546 Notebook.prototype.restore_checkpoint_success = function () {
2490 Notebook.prototype.restore_checkpoint_success = function () {
2547 this.events.trigger('checkpoint_restored.Notebook');
2491 this.events.trigger('checkpoint_restored.Notebook');
2548 this.load_notebook(this.notebook_path);
2492 this.load_notebook(this.notebook_path);
2549 };
2493 };
2550
2494
2551 /**
2495 /**
2552 * Delete a notebook checkpoint.
2496 * Delete a notebook checkpoint.
2553 *
2497 *
2554 * @method delete_checkpoint
2498 * @method delete_checkpoint
2555 * @param {String} checkpoint ID
2499 * @param {String} checkpoint ID
2556 */
2500 */
2557 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2501 Notebook.prototype.delete_checkpoint = function (checkpoint) {
2558 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2502 this.events.trigger('notebook_restoring.Notebook', checkpoint);
2559 var that = this;
2503 var that = this;
2560 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2504 this.contents.delete_checkpoint(this.notebook_path, checkpoint).then(
2561 $.proxy(this.delete_checkpoint_success, this),
2505 $.proxy(this.delete_checkpoint_success, this),
2562 function (error) {
2506 function (error) {
2563 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2507 that.events.trigger('checkpoint_delete_failed.Notebook', error);
2564 }
2508 }
2565 );
2509 );
2566 };
2510 };
2567
2511
2568 /**
2512 /**
2569 * Success callback for deleting a notebook checkpoint
2513 * Success callback for deleting a notebook checkpoint
2570 *
2514 *
2571 * @method delete_checkpoint_success
2515 * @method delete_checkpoint_success
2572 */
2516 */
2573 Notebook.prototype.delete_checkpoint_success = function () {
2517 Notebook.prototype.delete_checkpoint_success = function () {
2574 this.events.trigger('checkpoint_deleted.Notebook');
2518 this.events.trigger('checkpoint_deleted.Notebook');
2575 this.load_notebook(this.notebook_path);
2519 this.load_notebook(this.notebook_path);
2576 };
2520 };
2577
2521
2578
2522
2579 // For backwards compatability.
2523 // For backwards compatability.
2580 IPython.Notebook = Notebook;
2524 IPython.Notebook = Notebook;
2581
2525
2582 return {'Notebook': Notebook};
2526 return {'Notebook': Notebook};
2583 });
2527 });
@@ -1,325 +1,320 b''
1 define([
1 define([
2 'base/js/namespace',
2 'base/js/namespace',
3 'jquery',
3 'jquery',
4 'base/js/utils',
4 'base/js/utils',
5 'base/js/dialog',
5 'base/js/dialog',
6 'base/js/notificationarea',
6 'base/js/notificationarea',
7 'moment'
7 'moment'
8 ], function(IPython, $, utils, dialog, notificationarea, moment) {
8 ], function(IPython, $, utils, dialog, notificationarea, moment) {
9 "use strict";
9 "use strict";
10 var NotificationArea = notificationarea.NotificationArea;
10 var NotificationArea = notificationarea.NotificationArea;
11
11
12 var NotebookNotificationArea = function(selector, options) {
12 var NotebookNotificationArea = function(selector, options) {
13 NotificationArea.apply(this, [selector, options]);
13 NotificationArea.apply(this, [selector, options]);
14 this.save_widget = options.save_widget;
14 this.save_widget = options.save_widget;
15 this.notebook = options.notebook;
15 this.notebook = options.notebook;
16 this.keyboard_manager = options.keyboard_manager;
16 this.keyboard_manager = options.keyboard_manager;
17 }
17 }
18
18
19 NotebookNotificationArea.prototype = Object.create(NotificationArea.prototype);
19 NotebookNotificationArea.prototype = Object.create(NotificationArea.prototype);
20
20
21 /**
21 /**
22 * Initialize the default set of notification widgets.
22 * Initialize the default set of notification widgets.
23 *
23 *
24 * @method init_notification_widgets
24 * @method init_notification_widgets
25 */
25 */
26 NotebookNotificationArea.prototype.init_notification_widgets = function () {
26 NotebookNotificationArea.prototype.init_notification_widgets = function () {
27 this.init_kernel_notification_widget();
27 this.init_kernel_notification_widget();
28 this.init_notebook_notification_widget();
28 this.init_notebook_notification_widget();
29 };
29 };
30
30
31 /**
31 /**
32 * Initialize the notification widget for kernel status messages.
32 * Initialize the notification widget for kernel status messages.
33 *
33 *
34 * @method init_kernel_notification_widget
34 * @method init_kernel_notification_widget
35 */
35 */
36 NotebookNotificationArea.prototype.init_kernel_notification_widget = function () {
36 NotebookNotificationArea.prototype.init_kernel_notification_widget = function () {
37 var that = this;
37 var that = this;
38 var knw = this.new_notification_widget('kernel');
38 var knw = this.new_notification_widget('kernel');
39 var $kernel_ind_icon = $("#kernel_indicator_icon");
39 var $kernel_ind_icon = $("#kernel_indicator_icon");
40 var $modal_ind_icon = $("#modal_indicator_icon");
40 var $modal_ind_icon = $("#modal_indicator_icon");
41
41
42 // Command/Edit mode
42 // Command/Edit mode
43 this.events.on('edit_mode.Notebook', function () {
43 this.events.on('edit_mode.Notebook', function () {
44 that.save_widget.update_document_title();
44 that.save_widget.update_document_title();
45 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
45 $modal_ind_icon.attr('class','edit_mode_icon').attr('title','Edit Mode');
46 });
46 });
47
47
48 this.events.on('command_mode.Notebook', function () {
48 this.events.on('command_mode.Notebook', function () {
49 that.save_widget.update_document_title();
49 that.save_widget.update_document_title();
50 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
50 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
51 });
51 });
52
52
53 // Implicitly start off in Command mode, switching to Edit mode will trigger event
53 // Implicitly start off in Command mode, switching to Edit mode will trigger event
54 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
54 $modal_ind_icon.attr('class','command_mode_icon').attr('title','Command Mode');
55
55
56 // Kernel events
56 // Kernel events
57
57
58 // this can be either kernel_created.Kernel or kernel_created.Session
58 // this can be either kernel_created.Kernel or kernel_created.Session
59 this.events.on('kernel_created.Kernel kernel_created.Session', function () {
59 this.events.on('kernel_created.Kernel kernel_created.Session', function () {
60 knw.info("Kernel Created", 500);
60 knw.info("Kernel Created", 500);
61 });
61 });
62
62
63 this.events.on('kernel_reconnecting.Kernel', function () {
63 this.events.on('kernel_reconnecting.Kernel', function () {
64 knw.warning("Connecting to kernel");
64 knw.warning("Connecting to kernel");
65 });
65 });
66
66
67 this.events.on('kernel_connection_dead.Kernel', function (evt, info) {
67 this.events.on('kernel_connection_dead.Kernel', function (evt, info) {
68 knw.danger("Not Connected", undefined, function () {
68 knw.danger("Not Connected", undefined, function () {
69 // schedule reconnect a short time in the future, don't reconnect immediately
69 // schedule reconnect a short time in the future, don't reconnect immediately
70 setTimeout($.proxy(info.kernel.reconnect, info.kernel), 500);
70 setTimeout($.proxy(info.kernel.reconnect, info.kernel), 500);
71 }, {title: 'click to reconnect'});
71 }, {title: 'click to reconnect'});
72 });
72 });
73
73
74 this.events.on('kernel_connected.Kernel', function () {
74 this.events.on('kernel_connected.Kernel', function () {
75 knw.info("Connected", 500);
75 knw.info("Connected", 500);
76 });
76 });
77
77
78 this.events.on('kernel_restarting.Kernel', function () {
78 this.events.on('kernel_restarting.Kernel', function () {
79 that.save_widget.update_document_title();
79 that.save_widget.update_document_title();
80 knw.set_message("Restarting kernel", 2000);
80 knw.set_message("Restarting kernel", 2000);
81 });
81 });
82
82
83 this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
83 this.events.on('kernel_autorestarting.Kernel', function (evt, info) {
84 // Only show the dialog on the first restart attempt. This
84 // Only show the dialog on the first restart attempt. This
85 // number gets tracked by the `Kernel` object and passed
85 // number gets tracked by the `Kernel` object and passed
86 // along here, because we don't want to show the user 5
86 // along here, because we don't want to show the user 5
87 // dialogs saying the same thing (which is the number of
87 // dialogs saying the same thing (which is the number of
88 // times it tries restarting).
88 // times it tries restarting).
89 if (info.attempt === 1) {
89 if (info.attempt === 1) {
90
90
91 dialog.kernel_modal({
91 dialog.kernel_modal({
92 notebook: that.notebook,
92 notebook: that.notebook,
93 keyboard_manager: that.keyboard_manager,
93 keyboard_manager: that.keyboard_manager,
94 title: "Kernel Restarting",
94 title: "Kernel Restarting",
95 body: "The kernel appears to have died. It will restart automatically.",
95 body: "The kernel appears to have died. It will restart automatically.",
96 buttons: {
96 buttons: {
97 OK : {
97 OK : {
98 class : "btn-primary"
98 class : "btn-primary"
99 }
99 }
100 }
100 }
101 });
101 });
102 };
102 };
103
103
104 that.save_widget.update_document_title();
104 that.save_widget.update_document_title();
105 knw.danger("Dead kernel");
105 knw.danger("Dead kernel");
106 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
106 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
107 });
107 });
108
108
109 this.events.on('kernel_interrupting.Kernel', function () {
109 this.events.on('kernel_interrupting.Kernel', function () {
110 knw.set_message("Interrupting kernel", 2000);
110 knw.set_message("Interrupting kernel", 2000);
111 });
111 });
112
112
113 this.events.on('kernel_disconnected.Kernel', function () {
113 this.events.on('kernel_disconnected.Kernel', function () {
114 $kernel_ind_icon
114 $kernel_ind_icon
115 .attr('class', 'kernel_disconnected_icon')
115 .attr('class', 'kernel_disconnected_icon')
116 .attr('title', 'No Connection to Kernel');
116 .attr('title', 'No Connection to Kernel');
117 });
117 });
118
118
119 this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
119 this.events.on('kernel_connection_failed.Kernel', function (evt, info) {
120 // only show the dialog if this is the first failed
120 // only show the dialog if this is the first failed
121 // connect attempt, because the kernel will continue
121 // connect attempt, because the kernel will continue
122 // trying to reconnect and we don't want to spam the user
122 // trying to reconnect and we don't want to spam the user
123 // with messages
123 // with messages
124 if (info.attempt === 1) {
124 if (info.attempt === 1) {
125
125
126 var msg = "A connection to the notebook server could not be established." +
126 var msg = "A connection to the notebook server could not be established." +
127 " The notebook will continue trying to reconnect, but" +
127 " The notebook will continue trying to reconnect, but" +
128 " until it does, you will NOT be able to run code. Check your" +
128 " until it does, you will NOT be able to run code. Check your" +
129 " network connection or notebook server configuration.";
129 " network connection or notebook server configuration.";
130
130
131 dialog.kernel_modal({
131 dialog.kernel_modal({
132 title: "Connection failed",
132 title: "Connection failed",
133 body: msg,
133 body: msg,
134 keyboard_manager: that.keyboard_manager,
134 keyboard_manager: that.keyboard_manager,
135 notebook: that.notebook,
135 notebook: that.notebook,
136 buttons : {
136 buttons : {
137 "OK": {}
137 "OK": {}
138 }
138 }
139 });
139 });
140 }
140 }
141 });
141 });
142
142
143 this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
143 this.events.on('kernel_killed.Kernel kernel_killed.Session', function () {
144 that.save_widget.update_document_title();
144 that.save_widget.update_document_title();
145 knw.danger("Dead kernel");
145 knw.danger("Dead kernel");
146 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
146 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
147 });
147 });
148
148
149 this.events.on('kernel_dead.Kernel', function () {
149 this.events.on('kernel_dead.Kernel', function () {
150
150
151 var showMsg = function () {
151 var showMsg = function () {
152
152
153 var msg = 'The kernel has died, and the automatic restart has failed.' +
153 var msg = 'The kernel has died, and the automatic restart has failed.' +
154 ' It is possible the kernel cannot be restarted.' +
154 ' It is possible the kernel cannot be restarted.' +
155 ' If you are not able to restart the kernel, you will still be able to save' +
155 ' If you are not able to restart the kernel, you will still be able to save' +
156 ' the notebook, but running code will no longer work until the notebook' +
156 ' the notebook, but running code will no longer work until the notebook' +
157 ' is reopened.';
157 ' is reopened.';
158
158
159 dialog.kernel_modal({
159 dialog.kernel_modal({
160 title: "Dead kernel",
160 title: "Dead kernel",
161 body : msg,
161 body : msg,
162 keyboard_manager: that.keyboard_manager,
162 keyboard_manager: that.keyboard_manager,
163 notebook: that.notebook,
163 notebook: that.notebook,
164 buttons : {
164 buttons : {
165 "Manual Restart": {
165 "Manual Restart": {
166 class: "btn-danger",
166 class: "btn-danger",
167 click: function () {
167 click: function () {
168 that.notebook.start_session();
168 that.notebook.start_session();
169 }
169 }
170 },
170 },
171 "Don't restart": {}
171 "Don't restart": {}
172 }
172 }
173 });
173 });
174
174
175 return false;
175 return false;
176 };
176 };
177
177
178 that.save_widget.update_document_title();
178 that.save_widget.update_document_title();
179 knw.danger("Dead kernel", undefined, showMsg);
179 knw.danger("Dead kernel", undefined, showMsg);
180 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
180 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
181
181
182 showMsg();
182 showMsg();
183 });
183 });
184
184
185 this.events.on('kernel_dead.Session', function (evt, info) {
185 this.events.on('kernel_dead.Session', function (evt, info) {
186 var full = info.xhr.responseJSON.message;
186 var full = info.xhr.responseJSON.message;
187 var short = info.xhr.responseJSON.short_message || 'Kernel error';
187 var short = info.xhr.responseJSON.short_message || 'Kernel error';
188 var traceback = info.xhr.responseJSON.traceback;
188 var traceback = info.xhr.responseJSON.traceback;
189
189
190 var showMsg = function () {
190 var showMsg = function () {
191 var msg = $('<div/>').append($('<p/>').text(full));
191 var msg = $('<div/>').append($('<p/>').text(full));
192 var cm, cm_elem, cm_open;
192 var cm, cm_elem, cm_open;
193
193
194 if (traceback) {
194 if (traceback) {
195 cm_elem = $('<div/>')
195 cm_elem = $('<div/>')
196 .css('margin-top', '1em')
196 .css('margin-top', '1em')
197 .css('padding', '1em')
197 .css('padding', '1em')
198 .addClass('output_scroll');
198 .addClass('output_scroll');
199 msg.append(cm_elem);
199 msg.append(cm_elem);
200 cm = CodeMirror(cm_elem.get(0), {
200 cm = CodeMirror(cm_elem.get(0), {
201 mode: "python",
201 mode: "python",
202 readOnly : true
202 readOnly : true
203 });
203 });
204 cm.setValue(traceback);
204 cm.setValue(traceback);
205 cm_open = $.proxy(cm.refresh, cm);
205 cm_open = $.proxy(cm.refresh, cm);
206 }
206 }
207
207
208 dialog.kernel_modal({
208 dialog.kernel_modal({
209 title: "Failed to start the kernel",
209 title: "Failed to start the kernel",
210 body : msg,
210 body : msg,
211 keyboard_manager: that.keyboard_manager,
211 keyboard_manager: that.keyboard_manager,
212 notebook: that.notebook,
212 notebook: that.notebook,
213 open: cm_open,
213 open: cm_open,
214 buttons : {
214 buttons : {
215 "Ok": { class: 'btn-primary' }
215 "Ok": { class: 'btn-primary' }
216 }
216 }
217 });
217 });
218
218
219 return false;
219 return false;
220 };
220 };
221
221
222 that.save_widget.update_document_title();
222 that.save_widget.update_document_title();
223 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
223 $kernel_ind_icon.attr('class','kernel_dead_icon').attr('title','Kernel Dead');
224 knw.danger(short, undefined, showMsg);
224 knw.danger(short, undefined, showMsg);
225 });
225 });
226
226
227 this.events.on('kernel_starting.Kernel', function () {
227 this.events.on('kernel_starting.Kernel', function () {
228 window.document.title='(Starting) '+window.document.title;
228 window.document.title='(Starting) '+window.document.title;
229 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
229 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
230 knw.set_message("Kernel starting, please wait...");
230 knw.set_message("Kernel starting, please wait...");
231 });
231 });
232
232
233 this.events.on('kernel_ready.Kernel', function () {
233 this.events.on('kernel_ready.Kernel', function () {
234 that.save_widget.update_document_title();
234 that.save_widget.update_document_title();
235 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
235 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
236 knw.info("Kernel ready", 500);
236 knw.info("Kernel ready", 500);
237 });
237 });
238
238
239 this.events.on('kernel_idle.Kernel', function () {
239 this.events.on('kernel_idle.Kernel', function () {
240 that.save_widget.update_document_title();
240 that.save_widget.update_document_title();
241 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
241 $kernel_ind_icon.attr('class','kernel_idle_icon').attr('title','Kernel Idle');
242 });
242 });
243
243
244 this.events.on('kernel_busy.Kernel', function () {
244 this.events.on('kernel_busy.Kernel', function () {
245 window.document.title='(Busy) '+window.document.title;
245 window.document.title='(Busy) '+window.document.title;
246 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
246 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
247 });
247 });
248
248
249 // Start the kernel indicator in the busy state, and send a kernel_info request.
249 // Start the kernel indicator in the busy state, and send a kernel_info request.
250 // When the kernel_info reply arrives, the kernel is idle.
250 // When the kernel_info reply arrives, the kernel is idle.
251 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
251 $kernel_ind_icon.attr('class','kernel_busy_icon').attr('title','Kernel Busy');
252 };
252 };
253
253
254 /**
254 /**
255 * Initialize the notification widget for notebook status messages.
255 * Initialize the notification widget for notebook status messages.
256 *
256 *
257 * @method init_notebook_notification_widget
257 * @method init_notebook_notification_widget
258 */
258 */
259 NotebookNotificationArea.prototype.init_notebook_notification_widget = function () {
259 NotebookNotificationArea.prototype.init_notebook_notification_widget = function () {
260 var nnw = this.new_notification_widget('notebook');
260 var nnw = this.new_notification_widget('notebook');
261
261
262 // Notebook events
262 // Notebook events
263 this.events.on('notebook_loading.Notebook', function () {
263 this.events.on('notebook_loading.Notebook', function () {
264 nnw.set_message("Loading notebook",500);
264 nnw.set_message("Loading notebook",500);
265 });
265 });
266 this.events.on('notebook_loaded.Notebook', function () {
266 this.events.on('notebook_loaded.Notebook', function () {
267 nnw.set_message("Notebook loaded",500);
267 nnw.set_message("Notebook loaded",500);
268 });
268 });
269 this.events.on('notebook_saving.Notebook', function () {
269 this.events.on('notebook_saving.Notebook', function () {
270 nnw.set_message("Saving notebook",500);
270 nnw.set_message("Saving notebook",500);
271 });
271 });
272 this.events.on('notebook_saved.Notebook', function () {
272 this.events.on('notebook_saved.Notebook', function () {
273 nnw.set_message("Notebook saved",2000);
273 nnw.set_message("Notebook saved",2000);
274 });
274 });
275 this.events.on('notebook_save_failed.Notebook', function (evt, error) {
275 this.events.on('notebook_save_failed.Notebook', function (evt, error) {
276 nnw.warning(error.message || "Notebook save failed");
276 nnw.warning(error.message || "Notebook save failed");
277 });
277 });
278 this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
278 this.events.on('notebook_copy_failed.Notebook', function (evt, error) {
279 nnw.warning(error.message || "Notebook copy failed");
279 nnw.warning(error.message || "Notebook copy failed");
280 });
280 });
281
281
282 this.events.on('toggle_other_client_output.Notebook', function(evt, include_output) {
283 var msg = (include_output? "Showing": "Ignoring") + " output from other clients";
284 nnw.set_message(msg, 2000);
285 });
286
287 // Checkpoint events
282 // Checkpoint events
288 this.events.on('checkpoint_created.Notebook', function (evt, data) {
283 this.events.on('checkpoint_created.Notebook', function (evt, data) {
289 var msg = "Checkpoint created";
284 var msg = "Checkpoint created";
290 if (data.last_modified) {
285 if (data.last_modified) {
291 var d = new Date(data.last_modified);
286 var d = new Date(data.last_modified);
292 msg = msg + ": " + moment(d).format("HH:mm:ss");
287 msg = msg + ": " + moment(d).format("HH:mm:ss");
293 }
288 }
294 nnw.set_message(msg, 2000);
289 nnw.set_message(msg, 2000);
295 });
290 });
296 this.events.on('checkpoint_failed.Notebook', function () {
291 this.events.on('checkpoint_failed.Notebook', function () {
297 nnw.warning("Checkpoint failed");
292 nnw.warning("Checkpoint failed");
298 });
293 });
299 this.events.on('checkpoint_deleted.Notebook', function () {
294 this.events.on('checkpoint_deleted.Notebook', function () {
300 nnw.set_message("Checkpoint deleted", 500);
295 nnw.set_message("Checkpoint deleted", 500);
301 });
296 });
302 this.events.on('checkpoint_delete_failed.Notebook', function () {
297 this.events.on('checkpoint_delete_failed.Notebook', function () {
303 nnw.warning("Checkpoint delete failed");
298 nnw.warning("Checkpoint delete failed");
304 });
299 });
305 this.events.on('checkpoint_restoring.Notebook', function () {
300 this.events.on('checkpoint_restoring.Notebook', function () {
306 nnw.set_message("Restoring to checkpoint...", 500);
301 nnw.set_message("Restoring to checkpoint...", 500);
307 });
302 });
308 this.events.on('checkpoint_restore_failed.Notebook', function () {
303 this.events.on('checkpoint_restore_failed.Notebook', function () {
309 nnw.warning("Checkpoint restore failed");
304 nnw.warning("Checkpoint restore failed");
310 });
305 });
311
306
312 // Autosave events
307 // Autosave events
313 this.events.on('autosave_disabled.Notebook', function () {
308 this.events.on('autosave_disabled.Notebook', function () {
314 nnw.set_message("Autosave disabled", 2000);
309 nnw.set_message("Autosave disabled", 2000);
315 });
310 });
316 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
311 this.events.on('autosave_enabled.Notebook', function (evt, interval) {
317 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
312 nnw.set_message("Saving every " + interval / 1000 + "s", 1000);
318 });
313 });
319 };
314 };
320
315
321 // Backwards compatibility.
316 // Backwards compatibility.
322 IPython.NotificationArea = NotebookNotificationArea;
317 IPython.NotificationArea = NotebookNotificationArea;
323
318
324 return {'NotebookNotificationArea': NotebookNotificationArea};
319 return {'NotebookNotificationArea': NotebookNotificationArea};
325 });
320 });
@@ -1,1077 +1,1074 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 'base/js/utils',
7 'base/js/utils',
8 './comm',
8 './comm',
9 './serialize',
9 './serialize',
10 'widgets/js/init'
10 'widgets/js/init'
11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
11 ], function(IPython, $, utils, comm, serialize, widgetmanager) {
12 "use strict";
12 "use strict";
13
13
14 /**
14 /**
15 * A Kernel class to communicate with the Python kernel. This
15 * A Kernel class to communicate with the Python kernel. This
16 * should generally not be constructed directly, but be created
16 * should generally not be constructed directly, but be created
17 * by. the `Session` object. Once created, this object should be
17 * by. the `Session` object. Once created, this object should be
18 * used to communicate with the kernel.
18 * used to communicate with the kernel.
19 *
19 *
20 * @class Kernel
20 * @class Kernel
21 * @param {string} kernel_service_url - the URL to access the kernel REST api
21 * @param {string} kernel_service_url - the URL to access the kernel REST api
22 * @param {string} ws_url - the websockets URL
22 * @param {string} ws_url - the websockets URL
23 * @param {Notebook} notebook - notebook object
23 * @param {Notebook} notebook - notebook object
24 * @param {string} name - the kernel type (e.g. python3)
24 * @param {string} name - the kernel type (e.g. python3)
25 */
25 */
26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
26 var Kernel = function (kernel_service_url, ws_url, notebook, name) {
27 this.events = notebook.events;
27 this.events = notebook.events;
28
28
29 this.id = null;
29 this.id = null;
30 this.name = name;
30 this.name = name;
31
31
32 this.channels = {
32 this.channels = {
33 'shell': null,
33 'shell': null,
34 'iopub': null,
34 'iopub': null,
35 'stdin': null
35 'stdin': null
36 };
36 };
37
37
38 this.kernel_service_url = kernel_service_url;
38 this.kernel_service_url = kernel_service_url;
39 this.kernel_url = null;
39 this.kernel_url = null;
40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
40 this.ws_url = ws_url || IPython.utils.get_body_data("wsUrl");
41 if (!this.ws_url) {
41 if (!this.ws_url) {
42 // trailing 's' in https will become wss for secure web sockets
42 // trailing 's' in https will become wss for secure web sockets
43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
43 this.ws_url = location.protocol.replace('http', 'ws') + "//" + location.host;
44 }
44 }
45
45
46 this.username = "username";
46 this.username = "username";
47 this.session_id = utils.uuid();
47 this.session_id = utils.uuid();
48 this._msg_callbacks = {};
48 this._msg_callbacks = {};
49 this.info_reply = {}; // kernel_info_reply stored here after starting
49 this.info_reply = {}; // kernel_info_reply stored here after starting
50 this.unsolicited_msg_callback = null;
51
50
52 if (typeof(WebSocket) !== 'undefined') {
51 if (typeof(WebSocket) !== 'undefined') {
53 this.WebSocket = WebSocket;
52 this.WebSocket = WebSocket;
54 } else if (typeof(MozWebSocket) !== 'undefined') {
53 } else if (typeof(MozWebSocket) !== 'undefined') {
55 this.WebSocket = MozWebSocket;
54 this.WebSocket = MozWebSocket;
56 } else {
55 } else {
57 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.');
56 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.');
58 }
57 }
59
58
60 this.bind_events();
59 this.bind_events();
61 this.init_iopub_handlers();
60 this.init_iopub_handlers();
62 this.comm_manager = new comm.CommManager(this);
61 this.comm_manager = new comm.CommManager(this);
63 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
62 this.widget_manager = new widgetmanager.WidgetManager(this.comm_manager, notebook);
64
63
65 this.last_msg_id = null;
64 this.last_msg_id = null;
66 this.last_msg_callbacks = {};
65 this.last_msg_callbacks = {};
67
66
68 this._autorestart_attempt = 0;
67 this._autorestart_attempt = 0;
69 this._reconnect_attempt = 0;
68 this._reconnect_attempt = 0;
70 this.reconnect_limit = 7;
69 this.reconnect_limit = 7;
71 };
70 };
72
71
73 /**
72 /**
74 * @function _get_msg
73 * @function _get_msg
75 */
74 */
76 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
75 Kernel.prototype._get_msg = function (msg_type, content, metadata, buffers) {
77 var msg = {
76 var msg = {
78 header : {
77 header : {
79 msg_id : utils.uuid(),
78 msg_id : utils.uuid(),
80 username : this.username,
79 username : this.username,
81 session : this.session_id,
80 session : this.session_id,
82 msg_type : msg_type,
81 msg_type : msg_type,
83 version : "5.0"
82 version : "5.0"
84 },
83 },
85 metadata : metadata || {},
84 metadata : metadata || {},
86 content : content,
85 content : content,
87 buffers : buffers || [],
86 buffers : buffers || [],
88 parent_header : {}
87 parent_header : {}
89 };
88 };
90 return msg;
89 return msg;
91 };
90 };
92
91
93 /**
92 /**
94 * @function bind_events
93 * @function bind_events
95 */
94 */
96 Kernel.prototype.bind_events = function () {
95 Kernel.prototype.bind_events = function () {
97 var that = this;
96 var that = this;
98 this.events.on('send_input_reply.Kernel', function(evt, data) {
97 this.events.on('send_input_reply.Kernel', function(evt, data) {
99 that.send_input_reply(data);
98 that.send_input_reply(data);
100 });
99 });
101
100
102 var record_status = function (evt, info) {
101 var record_status = function (evt, info) {
103 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
102 console.log('Kernel: ' + evt.type + ' (' + info.kernel.id + ')');
104 };
103 };
105
104
106 this.events.on('kernel_created.Kernel', record_status);
105 this.events.on('kernel_created.Kernel', record_status);
107 this.events.on('kernel_reconnecting.Kernel', record_status);
106 this.events.on('kernel_reconnecting.Kernel', record_status);
108 this.events.on('kernel_connected.Kernel', record_status);
107 this.events.on('kernel_connected.Kernel', record_status);
109 this.events.on('kernel_starting.Kernel', record_status);
108 this.events.on('kernel_starting.Kernel', record_status);
110 this.events.on('kernel_restarting.Kernel', record_status);
109 this.events.on('kernel_restarting.Kernel', record_status);
111 this.events.on('kernel_autorestarting.Kernel', record_status);
110 this.events.on('kernel_autorestarting.Kernel', record_status);
112 this.events.on('kernel_interrupting.Kernel', record_status);
111 this.events.on('kernel_interrupting.Kernel', record_status);
113 this.events.on('kernel_disconnected.Kernel', record_status);
112 this.events.on('kernel_disconnected.Kernel', record_status);
114 // these are commented out because they are triggered a lot, but can
113 // these are commented out because they are triggered a lot, but can
115 // be uncommented for debugging purposes
114 // be uncommented for debugging purposes
116 //this.events.on('kernel_idle.Kernel', record_status);
115 //this.events.on('kernel_idle.Kernel', record_status);
117 //this.events.on('kernel_busy.Kernel', record_status);
116 //this.events.on('kernel_busy.Kernel', record_status);
118 this.events.on('kernel_ready.Kernel', record_status);
117 this.events.on('kernel_ready.Kernel', record_status);
119 this.events.on('kernel_killed.Kernel', record_status);
118 this.events.on('kernel_killed.Kernel', record_status);
120 this.events.on('kernel_dead.Kernel', record_status);
119 this.events.on('kernel_dead.Kernel', record_status);
121
120
122 this.events.on('kernel_ready.Kernel', function () {
121 this.events.on('kernel_ready.Kernel', function () {
123 that._autorestart_attempt = 0;
122 that._autorestart_attempt = 0;
124 });
123 });
125 this.events.on('kernel_connected.Kernel', function () {
124 this.events.on('kernel_connected.Kernel', function () {
126 that._reconnect_attempt = 0;
125 that._reconnect_attempt = 0;
127 });
126 });
128 };
127 };
129
128
130 /**
129 /**
131 * Initialize the iopub handlers.
130 * Initialize the iopub handlers.
132 *
131 *
133 * @function init_iopub_handlers
132 * @function init_iopub_handlers
134 */
133 */
135 Kernel.prototype.init_iopub_handlers = function () {
134 Kernel.prototype.init_iopub_handlers = function () {
136 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
135 var output_msg_types = ['stream', 'display_data', 'execute_result', 'error'];
137 this._iopub_handlers = {};
136 this._iopub_handlers = {};
138 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
137 this.register_iopub_handler('status', $.proxy(this._handle_status_message, this));
139 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
138 this.register_iopub_handler('clear_output', $.proxy(this._handle_clear_output, this));
140 this.register_iopub_handler('execute_input', $.proxy(this._handle_input_message, this));
139 this.register_iopub_handler('execute_input', $.proxy(this._handle_input_message, this));
141
140
142 for (var i=0; i < output_msg_types.length; i++) {
141 for (var i=0; i < output_msg_types.length; i++) {
143 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
142 this.register_iopub_handler(output_msg_types[i], $.proxy(this._handle_output_message, this));
144 }
143 }
145 };
144 };
146
145
147 /**
146 /**
148 * GET /api/kernels
147 * GET /api/kernels
149 *
148 *
150 * Get the list of running kernels.
149 * Get the list of running kernels.
151 *
150 *
152 * @function list
151 * @function list
153 * @param {function} [success] - function executed on ajax success
152 * @param {function} [success] - function executed on ajax success
154 * @param {function} [error] - functon executed on ajax error
153 * @param {function} [error] - functon executed on ajax error
155 */
154 */
156 Kernel.prototype.list = function (success, error) {
155 Kernel.prototype.list = function (success, error) {
157 $.ajax(this.kernel_service_url, {
156 $.ajax(this.kernel_service_url, {
158 processData: false,
157 processData: false,
159 cache: false,
158 cache: false,
160 type: "GET",
159 type: "GET",
161 dataType: "json",
160 dataType: "json",
162 success: success,
161 success: success,
163 error: this._on_error(error)
162 error: this._on_error(error)
164 });
163 });
165 };
164 };
166
165
167 /**
166 /**
168 * POST /api/kernels
167 * POST /api/kernels
169 *
168 *
170 * Start a new kernel.
169 * Start a new kernel.
171 *
170 *
172 * In general this shouldn't be used -- the kernel should be
171 * In general this shouldn't be used -- the kernel should be
173 * started through the session API. If you use this function and
172 * started through the session API. If you use this function and
174 * are also using the session API then your session and kernel
173 * are also using the session API then your session and kernel
175 * WILL be out of sync!
174 * WILL be out of sync!
176 *
175 *
177 * @function start
176 * @function start
178 * @param {params} [Object] - parameters to include in the query string
177 * @param {params} [Object] - parameters to include in the query string
179 * @param {function} [success] - function executed on ajax success
178 * @param {function} [success] - function executed on ajax success
180 * @param {function} [error] - functon executed on ajax error
179 * @param {function} [error] - functon executed on ajax error
181 */
180 */
182 Kernel.prototype.start = function (params, success, error) {
181 Kernel.prototype.start = function (params, success, error) {
183 var url = this.kernel_service_url;
182 var url = this.kernel_service_url;
184 var qs = $.param(params || {}); // query string for sage math stuff
183 var qs = $.param(params || {}); // query string for sage math stuff
185 if (qs !== "") {
184 if (qs !== "") {
186 url = url + "?" + qs;
185 url = url + "?" + qs;
187 }
186 }
188
187
189 var that = this;
188 var that = this;
190 var on_success = function (data, status, xhr) {
189 var on_success = function (data, status, xhr) {
191 that.events.trigger('kernel_created.Kernel', {kernel: that});
190 that.events.trigger('kernel_created.Kernel', {kernel: that});
192 that._kernel_created(data);
191 that._kernel_created(data);
193 if (success) {
192 if (success) {
194 success(data, status, xhr);
193 success(data, status, xhr);
195 }
194 }
196 };
195 };
197
196
198 $.ajax(url, {
197 $.ajax(url, {
199 processData: false,
198 processData: false,
200 cache: false,
199 cache: false,
201 type: "POST",
200 type: "POST",
202 data: JSON.stringify({name: this.name}),
201 data: JSON.stringify({name: this.name}),
203 dataType: "json",
202 dataType: "json",
204 success: this._on_success(on_success),
203 success: this._on_success(on_success),
205 error: this._on_error(error)
204 error: this._on_error(error)
206 });
205 });
207
206
208 return url;
207 return url;
209 };
208 };
210
209
211 /**
210 /**
212 * GET /api/kernels/[:kernel_id]
211 * GET /api/kernels/[:kernel_id]
213 *
212 *
214 * Get information about the kernel.
213 * Get information about the kernel.
215 *
214 *
216 * @function get_info
215 * @function get_info
217 * @param {function} [success] - function executed on ajax success
216 * @param {function} [success] - function executed on ajax success
218 * @param {function} [error] - functon executed on ajax error
217 * @param {function} [error] - functon executed on ajax error
219 */
218 */
220 Kernel.prototype.get_info = function (success, error) {
219 Kernel.prototype.get_info = function (success, error) {
221 $.ajax(this.kernel_url, {
220 $.ajax(this.kernel_url, {
222 processData: false,
221 processData: false,
223 cache: false,
222 cache: false,
224 type: "GET",
223 type: "GET",
225 dataType: "json",
224 dataType: "json",
226 success: this._on_success(success),
225 success: this._on_success(success),
227 error: this._on_error(error)
226 error: this._on_error(error)
228 });
227 });
229 };
228 };
230
229
231 /**
230 /**
232 * DELETE /api/kernels/[:kernel_id]
231 * DELETE /api/kernels/[:kernel_id]
233 *
232 *
234 * Shutdown the kernel.
233 * Shutdown the kernel.
235 *
234 *
236 * If you are also using sessions, then this function shoul NOT be
235 * If you are also using sessions, then this function shoul NOT be
237 * used. Instead, use Session.delete. Otherwise, the session and
236 * used. Instead, use Session.delete. Otherwise, the session and
238 * kernel WILL be out of sync.
237 * kernel WILL be out of sync.
239 *
238 *
240 * @function kill
239 * @function kill
241 * @param {function} [success] - function executed on ajax success
240 * @param {function} [success] - function executed on ajax success
242 * @param {function} [error] - functon executed on ajax error
241 * @param {function} [error] - functon executed on ajax error
243 */
242 */
244 Kernel.prototype.kill = function (success, error) {
243 Kernel.prototype.kill = function (success, error) {
245 this.events.trigger('kernel_killed.Kernel', {kernel: this});
244 this.events.trigger('kernel_killed.Kernel', {kernel: this});
246 this._kernel_dead();
245 this._kernel_dead();
247 $.ajax(this.kernel_url, {
246 $.ajax(this.kernel_url, {
248 processData: false,
247 processData: false,
249 cache: false,
248 cache: false,
250 type: "DELETE",
249 type: "DELETE",
251 dataType: "json",
250 dataType: "json",
252 success: this._on_success(success),
251 success: this._on_success(success),
253 error: this._on_error(error)
252 error: this._on_error(error)
254 });
253 });
255 };
254 };
256
255
257 /**
256 /**
258 * POST /api/kernels/[:kernel_id]/interrupt
257 * POST /api/kernels/[:kernel_id]/interrupt
259 *
258 *
260 * Interrupt the kernel.
259 * Interrupt the kernel.
261 *
260 *
262 * @function interrupt
261 * @function interrupt
263 * @param {function} [success] - function executed on ajax success
262 * @param {function} [success] - function executed on ajax success
264 * @param {function} [error] - functon executed on ajax error
263 * @param {function} [error] - functon executed on ajax error
265 */
264 */
266 Kernel.prototype.interrupt = function (success, error) {
265 Kernel.prototype.interrupt = function (success, error) {
267 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
266 this.events.trigger('kernel_interrupting.Kernel', {kernel: this});
268
267
269 var that = this;
268 var that = this;
270 var on_success = function (data, status, xhr) {
269 var on_success = function (data, status, xhr) {
271 /**
270 /**
272 * get kernel info so we know what state the kernel is in
271 * get kernel info so we know what state the kernel is in
273 */
272 */
274 that.kernel_info();
273 that.kernel_info();
275 if (success) {
274 if (success) {
276 success(data, status, xhr);
275 success(data, status, xhr);
277 }
276 }
278 };
277 };
279
278
280 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
279 var url = utils.url_join_encode(this.kernel_url, 'interrupt');
281 $.ajax(url, {
280 $.ajax(url, {
282 processData: false,
281 processData: false,
283 cache: false,
282 cache: false,
284 type: "POST",
283 type: "POST",
285 dataType: "json",
284 dataType: "json",
286 success: this._on_success(on_success),
285 success: this._on_success(on_success),
287 error: this._on_error(error)
286 error: this._on_error(error)
288 });
287 });
289 };
288 };
290
289
291 Kernel.prototype.restart = function (success, error) {
290 Kernel.prototype.restart = function (success, error) {
292 /**
291 /**
293 * POST /api/kernels/[:kernel_id]/restart
292 * POST /api/kernels/[:kernel_id]/restart
294 *
293 *
295 * Restart the kernel.
294 * Restart the kernel.
296 *
295 *
297 * @function interrupt
296 * @function interrupt
298 * @param {function} [success] - function executed on ajax success
297 * @param {function} [success] - function executed on ajax success
299 * @param {function} [error] - functon executed on ajax error
298 * @param {function} [error] - functon executed on ajax error
300 */
299 */
301 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
300 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
302 this.stop_channels();
301 this.stop_channels();
303
302
304 var that = this;
303 var that = this;
305 var on_success = function (data, status, xhr) {
304 var on_success = function (data, status, xhr) {
306 that.events.trigger('kernel_created.Kernel', {kernel: that});
305 that.events.trigger('kernel_created.Kernel', {kernel: that});
307 that._kernel_created(data);
306 that._kernel_created(data);
308 if (success) {
307 if (success) {
309 success(data, status, xhr);
308 success(data, status, xhr);
310 }
309 }
311 };
310 };
312
311
313 var on_error = function (xhr, status, err) {
312 var on_error = function (xhr, status, err) {
314 that.events.trigger('kernel_dead.Kernel', {kernel: that});
313 that.events.trigger('kernel_dead.Kernel', {kernel: that});
315 that._kernel_dead();
314 that._kernel_dead();
316 if (error) {
315 if (error) {
317 error(xhr, status, err);
316 error(xhr, status, err);
318 }
317 }
319 };
318 };
320
319
321 var url = utils.url_join_encode(this.kernel_url, 'restart');
320 var url = utils.url_join_encode(this.kernel_url, 'restart');
322 $.ajax(url, {
321 $.ajax(url, {
323 processData: false,
322 processData: false,
324 cache: false,
323 cache: false,
325 type: "POST",
324 type: "POST",
326 dataType: "json",
325 dataType: "json",
327 success: this._on_success(on_success),
326 success: this._on_success(on_success),
328 error: this._on_error(on_error)
327 error: this._on_error(on_error)
329 });
328 });
330 };
329 };
331
330
332 Kernel.prototype.reconnect = function () {
331 Kernel.prototype.reconnect = function () {
333 /**
332 /**
334 * Reconnect to a disconnected kernel. This is not actually a
333 * Reconnect to a disconnected kernel. This is not actually a
335 * standard HTTP request, but useful function nonetheless for
334 * standard HTTP request, but useful function nonetheless for
336 * reconnecting to the kernel if the connection is somehow lost.
335 * reconnecting to the kernel if the connection is somehow lost.
337 *
336 *
338 * @function reconnect
337 * @function reconnect
339 */
338 */
340 if (this.is_connected()) {
339 if (this.is_connected()) {
341 return;
340 return;
342 }
341 }
343 this._reconnect_attempt = this._reconnect_attempt + 1;
342 this._reconnect_attempt = this._reconnect_attempt + 1;
344 this.events.trigger('kernel_reconnecting.Kernel', {
343 this.events.trigger('kernel_reconnecting.Kernel', {
345 kernel: this,
344 kernel: this,
346 attempt: this._reconnect_attempt,
345 attempt: this._reconnect_attempt,
347 });
346 });
348 this.start_channels();
347 this.start_channels();
349 };
348 };
350
349
351 Kernel.prototype._on_success = function (success) {
350 Kernel.prototype._on_success = function (success) {
352 /**
351 /**
353 * Handle a successful AJAX request by updating the kernel id and
352 * Handle a successful AJAX request by updating the kernel id and
354 * name from the response, and then optionally calling a provided
353 * name from the response, and then optionally calling a provided
355 * callback.
354 * callback.
356 *
355 *
357 * @function _on_success
356 * @function _on_success
358 * @param {function} success - callback
357 * @param {function} success - callback
359 */
358 */
360 var that = this;
359 var that = this;
361 return function (data, status, xhr) {
360 return function (data, status, xhr) {
362 if (data) {
361 if (data) {
363 that.id = data.id;
362 that.id = data.id;
364 that.name = data.name;
363 that.name = data.name;
365 }
364 }
366 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
365 that.kernel_url = utils.url_join_encode(that.kernel_service_url, that.id);
367 if (success) {
366 if (success) {
368 success(data, status, xhr);
367 success(data, status, xhr);
369 }
368 }
370 };
369 };
371 };
370 };
372
371
373 Kernel.prototype._on_error = function (error) {
372 Kernel.prototype._on_error = function (error) {
374 /**
373 /**
375 * Handle a failed AJAX request by logging the error message, and
374 * Handle a failed AJAX request by logging the error message, and
376 * then optionally calling a provided callback.
375 * then optionally calling a provided callback.
377 *
376 *
378 * @function _on_error
377 * @function _on_error
379 * @param {function} error - callback
378 * @param {function} error - callback
380 */
379 */
381 return function (xhr, status, err) {
380 return function (xhr, status, err) {
382 utils.log_ajax_error(xhr, status, err);
381 utils.log_ajax_error(xhr, status, err);
383 if (error) {
382 if (error) {
384 error(xhr, status, err);
383 error(xhr, status, err);
385 }
384 }
386 };
385 };
387 };
386 };
388
387
389 Kernel.prototype._kernel_created = function (data) {
388 Kernel.prototype._kernel_created = function (data) {
390 /**
389 /**
391 * Perform necessary tasks once the kernel has been started,
390 * Perform necessary tasks once the kernel has been started,
392 * including actually connecting to the kernel.
391 * including actually connecting to the kernel.
393 *
392 *
394 * @function _kernel_created
393 * @function _kernel_created
395 * @param {Object} data - information about the kernel including id
394 * @param {Object} data - information about the kernel including id
396 */
395 */
397 this.id = data.id;
396 this.id = data.id;
398 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
397 this.kernel_url = utils.url_join_encode(this.kernel_service_url, this.id);
399 this.start_channels();
398 this.start_channels();
400 };
399 };
401
400
402 Kernel.prototype._kernel_connected = function () {
401 Kernel.prototype._kernel_connected = function () {
403 /**
402 /**
404 * Perform necessary tasks once the connection to the kernel has
403 * Perform necessary tasks once the connection to the kernel has
405 * been established. This includes requesting information about
404 * been established. This includes requesting information about
406 * the kernel.
405 * the kernel.
407 *
406 *
408 * @function _kernel_connected
407 * @function _kernel_connected
409 */
408 */
410 this.events.trigger('kernel_connected.Kernel', {kernel: this});
409 this.events.trigger('kernel_connected.Kernel', {kernel: this});
411 this.events.trigger('kernel_starting.Kernel', {kernel: this});
410 this.events.trigger('kernel_starting.Kernel', {kernel: this});
412 // get kernel info so we know what state the kernel is in
411 // get kernel info so we know what state the kernel is in
413 var that = this;
412 var that = this;
414 this.kernel_info(function (reply) {
413 this.kernel_info(function (reply) {
415 that.info_reply = reply.content;
414 that.info_reply = reply.content;
416 that.events.trigger('kernel_ready.Kernel', {kernel: that});
415 that.events.trigger('kernel_ready.Kernel', {kernel: that});
417 });
416 });
418 };
417 };
419
418
420 Kernel.prototype._kernel_dead = function () {
419 Kernel.prototype._kernel_dead = function () {
421 /**
420 /**
422 * Perform necessary tasks after the kernel has died. This closing
421 * Perform necessary tasks after the kernel has died. This closing
423 * communication channels to the kernel if they are still somehow
422 * communication channels to the kernel if they are still somehow
424 * open.
423 * open.
425 *
424 *
426 * @function _kernel_dead
425 * @function _kernel_dead
427 */
426 */
428 this.stop_channels();
427 this.stop_channels();
429 };
428 };
430
429
431 Kernel.prototype.start_channels = function () {
430 Kernel.prototype.start_channels = function () {
432 /**
431 /**
433 * Start the `shell`and `iopub` channels.
432 * Start the `shell`and `iopub` channels.
434 * Will stop and restart them if they already exist.
433 * Will stop and restart them if they already exist.
435 *
434 *
436 * @function start_channels
435 * @function start_channels
437 */
436 */
438 var that = this;
437 var that = this;
439 this.stop_channels();
438 this.stop_channels();
440 var ws_host_url = this.ws_url + this.kernel_url;
439 var ws_host_url = this.ws_url + this.kernel_url;
441
440
442 console.log("Starting WebSockets:", ws_host_url);
441 console.log("Starting WebSockets:", ws_host_url);
443
442
444 var channel_url = function(channel) {
443 var channel_url = function(channel) {
445 return [
444 return [
446 that.ws_url,
445 that.ws_url,
447 utils.url_join_encode(that.kernel_url, channel),
446 utils.url_join_encode(that.kernel_url, channel),
448 "?session_id=" + that.session_id
447 "?session_id=" + that.session_id
449 ].join('');
448 ].join('');
450 };
449 };
451 this.channels.shell = new this.WebSocket(channel_url("shell"));
450 this.channels.shell = new this.WebSocket(channel_url("shell"));
452 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
451 this.channels.stdin = new this.WebSocket(channel_url("stdin"));
453 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
452 this.channels.iopub = new this.WebSocket(channel_url("iopub"));
454
453
455 var already_called_onclose = false; // only alert once
454 var already_called_onclose = false; // only alert once
456 var ws_closed_early = function(evt){
455 var ws_closed_early = function(evt){
457 if (already_called_onclose){
456 if (already_called_onclose){
458 return;
457 return;
459 }
458 }
460 already_called_onclose = true;
459 already_called_onclose = true;
461 if ( ! evt.wasClean ){
460 if ( ! evt.wasClean ){
462 // If the websocket was closed early, that could mean
461 // If the websocket was closed early, that could mean
463 // that the kernel is actually dead. Try getting
462 // that the kernel is actually dead. Try getting
464 // information about the kernel from the API call --
463 // information about the kernel from the API call --
465 // if that fails, then assume the kernel is dead,
464 // if that fails, then assume the kernel is dead,
466 // otherwise just follow the typical websocket closed
465 // otherwise just follow the typical websocket closed
467 // protocol.
466 // protocol.
468 that.get_info(function () {
467 that.get_info(function () {
469 that._ws_closed(ws_host_url, false);
468 that._ws_closed(ws_host_url, false);
470 }, function () {
469 }, function () {
471 that.events.trigger('kernel_dead.Kernel', {kernel: that});
470 that.events.trigger('kernel_dead.Kernel', {kernel: that});
472 that._kernel_dead();
471 that._kernel_dead();
473 });
472 });
474 }
473 }
475 };
474 };
476 var ws_closed_late = function(evt){
475 var ws_closed_late = function(evt){
477 if (already_called_onclose){
476 if (already_called_onclose){
478 return;
477 return;
479 }
478 }
480 already_called_onclose = true;
479 already_called_onclose = true;
481 if ( ! evt.wasClean ){
480 if ( ! evt.wasClean ){
482 that._ws_closed(ws_host_url, false);
481 that._ws_closed(ws_host_url, false);
483 }
482 }
484 };
483 };
485 var ws_error = function(evt){
484 var ws_error = function(evt){
486 if (already_called_onclose){
485 if (already_called_onclose){
487 return;
486 return;
488 }
487 }
489 already_called_onclose = true;
488 already_called_onclose = true;
490 that._ws_closed(ws_host_url, true);
489 that._ws_closed(ws_host_url, true);
491 };
490 };
492
491
493 for (var c in this.channels) {
492 for (var c in this.channels) {
494 this.channels[c].onopen = $.proxy(this._ws_opened, this);
493 this.channels[c].onopen = $.proxy(this._ws_opened, this);
495 this.channels[c].onclose = ws_closed_early;
494 this.channels[c].onclose = ws_closed_early;
496 this.channels[c].onerror = ws_error;
495 this.channels[c].onerror = ws_error;
497 }
496 }
498 // switch from early-close to late-close message after 1s
497 // switch from early-close to late-close message after 1s
499 setTimeout(function() {
498 setTimeout(function() {
500 for (var c in that.channels) {
499 for (var c in that.channels) {
501 if (that.channels[c] !== null) {
500 if (that.channels[c] !== null) {
502 that.channels[c].onclose = ws_closed_late;
501 that.channels[c].onclose = ws_closed_late;
503 }
502 }
504 }
503 }
505 }, 1000);
504 }, 1000);
506 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
505 this.channels.shell.onmessage = $.proxy(this._handle_shell_reply, this);
507 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
506 this.channels.iopub.onmessage = $.proxy(this._handle_iopub_message, this);
508 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
507 this.channels.stdin.onmessage = $.proxy(this._handle_input_request, this);
509 };
508 };
510
509
511 Kernel.prototype._ws_opened = function (evt) {
510 Kernel.prototype._ws_opened = function (evt) {
512 /**
511 /**
513 * Handle a websocket entering the open state,
512 * Handle a websocket entering the open state,
514 * signaling that the kernel is connected when all channels are open.
513 * signaling that the kernel is connected when all channels are open.
515 *
514 *
516 * @function _ws_opened
515 * @function _ws_opened
517 */
516 */
518 if (this.is_connected()) {
517 if (this.is_connected()) {
519 // all events ready, trigger started event.
518 // all events ready, trigger started event.
520 this._kernel_connected();
519 this._kernel_connected();
521 }
520 }
522 };
521 };
523
522
524 Kernel.prototype._ws_closed = function(ws_url, error) {
523 Kernel.prototype._ws_closed = function(ws_url, error) {
525 /**
524 /**
526 * Handle a websocket entering the closed state. This closes the
525 * Handle a websocket entering the closed state. This closes the
527 * other communication channels if they are open. If the websocket
526 * other communication channels if they are open. If the websocket
528 * was not closed due to an error, try to reconnect to the kernel.
527 * was not closed due to an error, try to reconnect to the kernel.
529 *
528 *
530 * @function _ws_closed
529 * @function _ws_closed
531 * @param {string} ws_url - the websocket url
530 * @param {string} ws_url - the websocket url
532 * @param {bool} error - whether the connection was closed due to an error
531 * @param {bool} error - whether the connection was closed due to an error
533 */
532 */
534 this.stop_channels();
533 this.stop_channels();
535
534
536 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
535 this.events.trigger('kernel_disconnected.Kernel', {kernel: this});
537 if (error) {
536 if (error) {
538 console.log('WebSocket connection failed: ', ws_url);
537 console.log('WebSocket connection failed: ', ws_url);
539 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
538 this.events.trigger('kernel_connection_failed.Kernel', {kernel: this, ws_url: ws_url, attempt: this._reconnect_attempt});
540 }
539 }
541 this._schedule_reconnect();
540 this._schedule_reconnect();
542 };
541 };
543
542
544 Kernel.prototype._schedule_reconnect = function () {
543 Kernel.prototype._schedule_reconnect = function () {
545 /**
544 /**
546 * function to call when kernel connection is lost
545 * function to call when kernel connection is lost
547 * schedules reconnect, or fires 'connection_dead' if reconnect limit is hit
546 * schedules reconnect, or fires 'connection_dead' if reconnect limit is hit
548 */
547 */
549 if (this._reconnect_attempt < this.reconnect_limit) {
548 if (this._reconnect_attempt < this.reconnect_limit) {
550 var timeout = Math.pow(2, this._reconnect_attempt);
549 var timeout = Math.pow(2, this._reconnect_attempt);
551 console.log("Connection lost, reconnecting in " + timeout + " seconds.");
550 console.log("Connection lost, reconnecting in " + timeout + " seconds.");
552 setTimeout($.proxy(this.reconnect, this), 1e3 * timeout);
551 setTimeout($.proxy(this.reconnect, this), 1e3 * timeout);
553 } else {
552 } else {
554 this.events.trigger('kernel_connection_dead.Kernel', {
553 this.events.trigger('kernel_connection_dead.Kernel', {
555 kernel: this,
554 kernel: this,
556 reconnect_attempt: this._reconnect_attempt,
555 reconnect_attempt: this._reconnect_attempt,
557 });
556 });
558 console.log("Failed to reconnect, giving up.");
557 console.log("Failed to reconnect, giving up.");
559 }
558 }
560 };
559 };
561
560
562 Kernel.prototype.stop_channels = function () {
561 Kernel.prototype.stop_channels = function () {
563 /**
562 /**
564 * Close the websocket channels. After successful close, the value
563 * Close the websocket channels. After successful close, the value
565 * in `this.channels[channel_name]` will be null.
564 * in `this.channels[channel_name]` will be null.
566 *
565 *
567 * @function stop_channels
566 * @function stop_channels
568 */
567 */
569 var that = this;
568 var that = this;
570 var close = function (c) {
569 var close = function (c) {
571 return function () {
570 return function () {
572 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
571 if (that.channels[c] && that.channels[c].readyState === WebSocket.CLOSED) {
573 that.channels[c] = null;
572 that.channels[c] = null;
574 }
573 }
575 };
574 };
576 };
575 };
577 for (var c in this.channels) {
576 for (var c in this.channels) {
578 if ( this.channels[c] !== null ) {
577 if ( this.channels[c] !== null ) {
579 if (this.channels[c].readyState === WebSocket.OPEN) {
578 if (this.channels[c].readyState === WebSocket.OPEN) {
580 this.channels[c].onclose = close(c);
579 this.channels[c].onclose = close(c);
581 this.channels[c].close();
580 this.channels[c].close();
582 } else {
581 } else {
583 close(c)();
582 close(c)();
584 }
583 }
585 }
584 }
586 }
585 }
587 };
586 };
588
587
589 Kernel.prototype.is_connected = function () {
588 Kernel.prototype.is_connected = function () {
590 /**
589 /**
591 * Check whether there is a connection to the kernel. This
590 * Check whether there is a connection to the kernel. This
592 * function only returns true if all channel objects have been
591 * function only returns true if all channel objects have been
593 * created and have a state of WebSocket.OPEN.
592 * created and have a state of WebSocket.OPEN.
594 *
593 *
595 * @function is_connected
594 * @function is_connected
596 * @returns {bool} - whether there is a connection
595 * @returns {bool} - whether there is a connection
597 */
596 */
598 for (var c in this.channels) {
597 for (var c in this.channels) {
599 // if any channel is not ready, then we're not connected
598 // if any channel is not ready, then we're not connected
600 if (this.channels[c] === null) {
599 if (this.channels[c] === null) {
601 return false;
600 return false;
602 }
601 }
603 if (this.channels[c].readyState !== WebSocket.OPEN) {
602 if (this.channels[c].readyState !== WebSocket.OPEN) {
604 return false;
603 return false;
605 }
604 }
606 }
605 }
607 return true;
606 return true;
608 };
607 };
609
608
610 Kernel.prototype.is_fully_disconnected = function () {
609 Kernel.prototype.is_fully_disconnected = function () {
611 /**
610 /**
612 * Check whether the connection to the kernel has been completely
611 * Check whether the connection to the kernel has been completely
613 * severed. This function only returns true if all channel objects
612 * severed. This function only returns true if all channel objects
614 * are null.
613 * are null.
615 *
614 *
616 * @function is_fully_disconnected
615 * @function is_fully_disconnected
617 * @returns {bool} - whether the kernel is fully disconnected
616 * @returns {bool} - whether the kernel is fully disconnected
618 */
617 */
619 for (var c in this.channels) {
618 for (var c in this.channels) {
620 if (this.channels[c] === null) {
619 if (this.channels[c] === null) {
621 return true;
620 return true;
622 }
621 }
623 }
622 }
624 return false;
623 return false;
625 };
624 };
626
625
627 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
626 Kernel.prototype.send_shell_message = function (msg_type, content, callbacks, metadata, buffers) {
628 /**
627 /**
629 * Send a message on the Kernel's shell channel
628 * Send a message on the Kernel's shell channel
630 *
629 *
631 * @function send_shell_message
630 * @function send_shell_message
632 */
631 */
633 if (!this.is_connected()) {
632 if (!this.is_connected()) {
634 throw new Error("kernel is not connected");
633 throw new Error("kernel is not connected");
635 }
634 }
636 var msg = this._get_msg(msg_type, content, metadata, buffers);
635 var msg = this._get_msg(msg_type, content, metadata, buffers);
637 this.channels.shell.send(serialize.serialize(msg));
636 this.channels.shell.send(serialize.serialize(msg));
638 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
637 this.set_callbacks_for_msg(msg.header.msg_id, callbacks);
639 return msg.header.msg_id;
638 return msg.header.msg_id;
640 };
639 };
641
640
642 Kernel.prototype.kernel_info = function (callback) {
641 Kernel.prototype.kernel_info = function (callback) {
643 /**
642 /**
644 * Get kernel info
643 * Get kernel info
645 *
644 *
646 * @function kernel_info
645 * @function kernel_info
647 * @param callback {function}
646 * @param callback {function}
648 *
647 *
649 * When calling this method, pass a callback function that expects one argument.
648 * When calling this method, pass a callback function that expects one argument.
650 * The callback will be passed the complete `kernel_info_reply` message documented
649 * The callback will be passed the complete `kernel_info_reply` message documented
651 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
650 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#kernel-info)
652 */
651 */
653 var callbacks;
652 var callbacks;
654 if (callback) {
653 if (callback) {
655 callbacks = { shell : { reply : callback } };
654 callbacks = { shell : { reply : callback } };
656 }
655 }
657 return this.send_shell_message("kernel_info_request", {}, callbacks);
656 return this.send_shell_message("kernel_info_request", {}, callbacks);
658 };
657 };
659
658
660 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
659 Kernel.prototype.inspect = function (code, cursor_pos, callback) {
661 /**
660 /**
662 * Get info on an object
661 * Get info on an object
663 *
662 *
664 * When calling this method, pass a callback function that expects one argument.
663 * When calling this method, pass a callback function that expects one argument.
665 * The callback will be passed the complete `inspect_reply` message documented
664 * The callback will be passed the complete `inspect_reply` message documented
666 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
665 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#object-information)
667 *
666 *
668 * @function inspect
667 * @function inspect
669 * @param code {string}
668 * @param code {string}
670 * @param cursor_pos {integer}
669 * @param cursor_pos {integer}
671 * @param callback {function}
670 * @param callback {function}
672 */
671 */
673 var callbacks;
672 var callbacks;
674 if (callback) {
673 if (callback) {
675 callbacks = { shell : { reply : callback } };
674 callbacks = { shell : { reply : callback } };
676 }
675 }
677
676
678 var content = {
677 var content = {
679 code : code,
678 code : code,
680 cursor_pos : cursor_pos,
679 cursor_pos : cursor_pos,
681 detail_level : 0
680 detail_level : 0
682 };
681 };
683 return this.send_shell_message("inspect_request", content, callbacks);
682 return this.send_shell_message("inspect_request", content, callbacks);
684 };
683 };
685
684
686 Kernel.prototype.execute = function (code, callbacks, options) {
685 Kernel.prototype.execute = function (code, callbacks, options) {
687 /**
686 /**
688 * Execute given code into kernel, and pass result to callback.
687 * Execute given code into kernel, and pass result to callback.
689 *
688 *
690 * @async
689 * @async
691 * @function execute
690 * @function execute
692 * @param {string} code
691 * @param {string} code
693 * @param [callbacks] {Object} With the following keys (all optional)
692 * @param [callbacks] {Object} With the following keys (all optional)
694 * @param callbacks.shell.reply {function}
693 * @param callbacks.shell.reply {function}
695 * @param callbacks.shell.payload.[payload_name] {function}
694 * @param callbacks.shell.payload.[payload_name] {function}
696 * @param callbacks.iopub.output {function}
695 * @param callbacks.iopub.output {function}
697 * @param callbacks.iopub.clear_output {function}
696 * @param callbacks.iopub.clear_output {function}
698 * @param callbacks.input {function}
697 * @param callbacks.input {function}
699 * @param {object} [options]
698 * @param {object} [options]
700 * @param [options.silent=false] {Boolean}
699 * @param [options.silent=false] {Boolean}
701 * @param [options.user_expressions=empty_dict] {Dict}
700 * @param [options.user_expressions=empty_dict] {Dict}
702 * @param [options.allow_stdin=false] {Boolean} true|false
701 * @param [options.allow_stdin=false] {Boolean} true|false
703 *
702 *
704 * @example
703 * @example
705 *
704 *
706 * The options object should contain the options for the execute
705 * The options object should contain the options for the execute
707 * call. Its default values are:
706 * call. Its default values are:
708 *
707 *
709 * options = {
708 * options = {
710 * silent : true,
709 * silent : true,
711 * user_expressions : {},
710 * user_expressions : {},
712 * allow_stdin : false
711 * allow_stdin : false
713 * }
712 * }
714 *
713 *
715 * When calling this method pass a callbacks structure of the
714 * When calling this method pass a callbacks structure of the
716 * form:
715 * form:
717 *
716 *
718 * callbacks = {
717 * callbacks = {
719 * shell : {
718 * shell : {
720 * reply : execute_reply_callback,
719 * reply : execute_reply_callback,
721 * payload : {
720 * payload : {
722 * set_next_input : set_next_input_callback,
721 * set_next_input : set_next_input_callback,
723 * }
722 * }
724 * },
723 * },
725 * iopub : {
724 * iopub : {
726 * output : output_callback,
725 * output : output_callback,
727 * clear_output : clear_output_callback,
726 * clear_output : clear_output_callback,
728 * },
727 * },
729 * input : raw_input_callback
728 * input : raw_input_callback
730 * }
729 * }
731 *
730 *
732 * Each callback will be passed the entire message as a single
731 * Each callback will be passed the entire message as a single
733 * arugment. Payload handlers will be passed the corresponding
732 * arugment. Payload handlers will be passed the corresponding
734 * payload and the execute_reply message.
733 * payload and the execute_reply message.
735 */
734 */
736 var content = {
735 var content = {
737 code : code,
736 code : code,
738 silent : true,
737 silent : true,
739 store_history : false,
738 store_history : false,
740 user_expressions : {},
739 user_expressions : {},
741 allow_stdin : false
740 allow_stdin : false
742 };
741 };
743 callbacks = callbacks || {};
742 callbacks = callbacks || {};
744 if (callbacks.input !== undefined) {
743 if (callbacks.input !== undefined) {
745 content.allow_stdin = true;
744 content.allow_stdin = true;
746 }
745 }
747 $.extend(true, content, options);
746 $.extend(true, content, options);
748 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
747 this.events.trigger('execution_request.Kernel', {kernel: this, content: content});
749 return this.send_shell_message("execute_request", content, callbacks);
748 return this.send_shell_message("execute_request", content, callbacks);
750 };
749 };
751
750
752 /**
751 /**
753 * When calling this method, pass a function to be called with the
752 * When calling this method, pass a function to be called with the
754 * `complete_reply` message as its only argument when it arrives.
753 * `complete_reply` message as its only argument when it arrives.
755 *
754 *
756 * `complete_reply` is documented
755 * `complete_reply` is documented
757 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
756 * [here](http://ipython.org/ipython-doc/dev/development/messaging.html#complete)
758 *
757 *
759 * @function complete
758 * @function complete
760 * @param code {string}
759 * @param code {string}
761 * @param cursor_pos {integer}
760 * @param cursor_pos {integer}
762 * @param callback {function}
761 * @param callback {function}
763 */
762 */
764 Kernel.prototype.complete = function (code, cursor_pos, callback) {
763 Kernel.prototype.complete = function (code, cursor_pos, callback) {
765 var callbacks;
764 var callbacks;
766 if (callback) {
765 if (callback) {
767 callbacks = { shell : { reply : callback } };
766 callbacks = { shell : { reply : callback } };
768 }
767 }
769 var content = {
768 var content = {
770 code : code,
769 code : code,
771 cursor_pos : cursor_pos
770 cursor_pos : cursor_pos
772 };
771 };
773 return this.send_shell_message("complete_request", content, callbacks);
772 return this.send_shell_message("complete_request", content, callbacks);
774 };
773 };
775
774
776 /**
775 /**
777 * @function send_input_reply
776 * @function send_input_reply
778 */
777 */
779 Kernel.prototype.send_input_reply = function (input) {
778 Kernel.prototype.send_input_reply = function (input) {
780 if (!this.is_connected()) {
779 if (!this.is_connected()) {
781 throw new Error("kernel is not connected");
780 throw new Error("kernel is not connected");
782 }
781 }
783 var content = {
782 var content = {
784 value : input
783 value : input
785 };
784 };
786 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
785 this.events.trigger('input_reply.Kernel', {kernel: this, content: content});
787 var msg = this._get_msg("input_reply", content);
786 var msg = this._get_msg("input_reply", content);
788 this.channels.stdin.send(serialize.serialize(msg));
787 this.channels.stdin.send(serialize.serialize(msg));
789 return msg.header.msg_id;
788 return msg.header.msg_id;
790 };
789 };
791
790
792 /**
791 /**
793 * @function register_iopub_handler
792 * @function register_iopub_handler
794 */
793 */
795 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
794 Kernel.prototype.register_iopub_handler = function (msg_type, callback) {
796 this._iopub_handlers[msg_type] = callback;
795 this._iopub_handlers[msg_type] = callback;
797 };
796 };
798
797
799 /**
798 /**
800 * Get the iopub handler for a specific message type.
799 * Get the iopub handler for a specific message type.
801 *
800 *
802 * @function get_iopub_handler
801 * @function get_iopub_handler
803 */
802 */
804 Kernel.prototype.get_iopub_handler = function (msg_type) {
803 Kernel.prototype.get_iopub_handler = function (msg_type) {
805 return this._iopub_handlers[msg_type];
804 return this._iopub_handlers[msg_type];
806 };
805 };
807
806
808 /**
807 /**
809 * Get callbacks for a specific message.
808 * Get callbacks for a specific message.
810 *
809 *
811 * @function get_callbacks_for_msg
810 * @function get_callbacks_for_msg
812 */
811 */
813 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
812 Kernel.prototype.get_callbacks_for_msg = function (msg_id) {
814 if (msg_id == this.last_msg_id) {
813 if (msg_id == this.last_msg_id) {
815 return this.last_msg_callbacks;
814 return this.last_msg_callbacks;
816 } else {
815 } else {
817 return this._msg_callbacks[msg_id];
816 return this._msg_callbacks[msg_id];
818 }
817 }
819 };
818 };
820
819
821 /**
820 /**
822 * Clear callbacks for a specific message.
821 * Clear callbacks for a specific message.
823 *
822 *
824 * @function clear_callbacks_for_msg
823 * @function clear_callbacks_for_msg
825 */
824 */
826 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
825 Kernel.prototype.clear_callbacks_for_msg = function (msg_id) {
827 if (this._msg_callbacks[msg_id] !== undefined ) {
826 if (this._msg_callbacks[msg_id] !== undefined ) {
828 delete this._msg_callbacks[msg_id];
827 delete this._msg_callbacks[msg_id];
829 }
828 }
830 };
829 };
831
830
832 /**
831 /**
833 * @function _finish_shell
832 * @function _finish_shell
834 */
833 */
835 Kernel.prototype._finish_shell = function (msg_id) {
834 Kernel.prototype._finish_shell = function (msg_id) {
836 var callbacks = this._msg_callbacks[msg_id];
835 var callbacks = this._msg_callbacks[msg_id];
837 if (callbacks !== undefined) {
836 if (callbacks !== undefined) {
838 callbacks.shell_done = true;
837 callbacks.shell_done = true;
839 if (callbacks.iopub_done) {
838 if (callbacks.iopub_done) {
840 this.clear_callbacks_for_msg(msg_id);
839 this.clear_callbacks_for_msg(msg_id);
841 }
840 }
842 }
841 }
843 };
842 };
844
843
845 /**
844 /**
846 * @function _finish_iopub
845 * @function _finish_iopub
847 */
846 */
848 Kernel.prototype._finish_iopub = function (msg_id) {
847 Kernel.prototype._finish_iopub = function (msg_id) {
849 var callbacks = this._msg_callbacks[msg_id];
848 var callbacks = this._msg_callbacks[msg_id];
850 if (callbacks !== undefined) {
849 if (callbacks !== undefined) {
851 callbacks.iopub_done = true;
850 callbacks.iopub_done = true;
852 if (callbacks.shell_done) {
851 if (callbacks.shell_done) {
853 this.clear_callbacks_for_msg(msg_id);
852 this.clear_callbacks_for_msg(msg_id);
854 }
853 }
855 }
854 }
856 };
855 };
857
856
858 /**
857 /**
859 * Set callbacks for a particular message.
858 * Set callbacks for a particular message.
860 * Callbacks should be a struct of the following form:
859 * Callbacks should be a struct of the following form:
861 * shell : {
860 * shell : {
862 *
861 *
863 * }
862 * }
864 *
863 *
865 * @function set_callbacks_for_msg
864 * @function set_callbacks_for_msg
866 */
865 */
867 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
866 Kernel.prototype.set_callbacks_for_msg = function (msg_id, callbacks) {
868 this.last_msg_id = msg_id;
867 this.last_msg_id = msg_id;
869 if (callbacks) {
868 if (callbacks) {
870 // shallow-copy mapping, because we will modify it at the top level
869 // shallow-copy mapping, because we will modify it at the top level
871 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
870 var cbcopy = this._msg_callbacks[msg_id] = this.last_msg_callbacks = {};
872 cbcopy.shell = callbacks.shell;
871 cbcopy.shell = callbacks.shell;
873 cbcopy.iopub = callbacks.iopub;
872 cbcopy.iopub = callbacks.iopub;
874 cbcopy.input = callbacks.input;
873 cbcopy.input = callbacks.input;
875 cbcopy.shell_done = (!callbacks.shell);
874 cbcopy.shell_done = (!callbacks.shell);
876 cbcopy.iopub_done = (!callbacks.iopub);
875 cbcopy.iopub_done = (!callbacks.iopub);
877 } else {
876 } else {
878 this.last_msg_callbacks = {};
877 this.last_msg_callbacks = {};
879 }
878 }
880 };
879 };
881
880
882 /**
881 /**
883 * @function _handle_shell_reply
882 * @function _handle_shell_reply
884 */
883 */
885 Kernel.prototype._handle_shell_reply = function (e) {
884 Kernel.prototype._handle_shell_reply = function (e) {
886 serialize.deserialize(e.data, $.proxy(this._finish_shell_reply, this));
885 serialize.deserialize(e.data, $.proxy(this._finish_shell_reply, this));
887 };
886 };
888
887
889 Kernel.prototype._finish_shell_reply = function (reply) {
888 Kernel.prototype._finish_shell_reply = function (reply) {
890 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
889 this.events.trigger('shell_reply.Kernel', {kernel: this, reply:reply});
891 var content = reply.content;
890 var content = reply.content;
892 var metadata = reply.metadata;
891 var metadata = reply.metadata;
893 var parent_id = reply.parent_header.msg_id;
892 var parent_id = reply.parent_header.msg_id;
894 var callbacks = this.get_callbacks_for_msg(parent_id);
893 var callbacks = this.get_callbacks_for_msg(parent_id);
895 if (!callbacks || !callbacks.shell) {
894 if (!callbacks || !callbacks.shell) {
896 return;
895 return;
897 }
896 }
898 var shell_callbacks = callbacks.shell;
897 var shell_callbacks = callbacks.shell;
899
898
900 // signal that shell callbacks are done
899 // signal that shell callbacks are done
901 this._finish_shell(parent_id);
900 this._finish_shell(parent_id);
902
901
903 if (shell_callbacks.reply !== undefined) {
902 if (shell_callbacks.reply !== undefined) {
904 shell_callbacks.reply(reply);
903 shell_callbacks.reply(reply);
905 }
904 }
906 if (content.payload && shell_callbacks.payload) {
905 if (content.payload && shell_callbacks.payload) {
907 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
906 this._handle_payloads(content.payload, shell_callbacks.payload, reply);
908 }
907 }
909 };
908 };
910
909
911 /**
910 /**
912 * @function _handle_payloads
911 * @function _handle_payloads
913 */
912 */
914 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
913 Kernel.prototype._handle_payloads = function (payloads, payload_callbacks, msg) {
915 var l = payloads.length;
914 var l = payloads.length;
916 // Payloads are handled by triggering events because we don't want the Kernel
915 // Payloads are handled by triggering events because we don't want the Kernel
917 // to depend on the Notebook or Pager classes.
916 // to depend on the Notebook or Pager classes.
918 for (var i=0; i<l; i++) {
917 for (var i=0; i<l; i++) {
919 var payload = payloads[i];
918 var payload = payloads[i];
920 var callback = payload_callbacks[payload.source];
919 var callback = payload_callbacks[payload.source];
921 if (callback) {
920 if (callback) {
922 callback(payload, msg);
921 callback(payload, msg);
923 }
922 }
924 }
923 }
925 };
924 };
926
925
927 /**
926 /**
928 * @function _handle_status_message
927 * @function _handle_status_message
929 */
928 */
930 Kernel.prototype._handle_status_message = function (msg) {
929 Kernel.prototype._handle_status_message = function (msg) {
931 var execution_state = msg.content.execution_state;
930 var execution_state = msg.content.execution_state;
932 var parent_id = msg.parent_header.msg_id;
931 var parent_id = msg.parent_header.msg_id;
933
932
934 // dispatch status msg callbacks, if any
933 // dispatch status msg callbacks, if any
935 var callbacks = this.get_callbacks_for_msg(parent_id);
934 var callbacks = this.get_callbacks_for_msg(parent_id);
936 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
935 if (callbacks && callbacks.iopub && callbacks.iopub.status) {
937 try {
936 try {
938 callbacks.iopub.status(msg);
937 callbacks.iopub.status(msg);
939 } catch (e) {
938 } catch (e) {
940 console.log("Exception in status msg handler", e, e.stack);
939 console.log("Exception in status msg handler", e, e.stack);
941 }
940 }
942 }
941 }
943
942
944 if (execution_state === 'busy') {
943 if (execution_state === 'busy') {
945 this.events.trigger('kernel_busy.Kernel', {kernel: this});
944 this.events.trigger('kernel_busy.Kernel', {kernel: this});
946
945
947 } else if (execution_state === 'idle') {
946 } else if (execution_state === 'idle') {
948 // signal that iopub callbacks are (probably) done
947 // signal that iopub callbacks are (probably) done
949 // async output may still arrive,
948 // async output may still arrive,
950 // but only for the most recent request
949 // but only for the most recent request
951 this._finish_iopub(parent_id);
950 this._finish_iopub(parent_id);
952
951
953 // trigger status_idle event
952 // trigger status_idle event
954 this.events.trigger('kernel_idle.Kernel', {kernel: this});
953 this.events.trigger('kernel_idle.Kernel', {kernel: this});
955
954
956 } else if (execution_state === 'starting') {
955 } else if (execution_state === 'starting') {
957 this.events.trigger('kernel_starting.Kernel', {kernel: this});
956 this.events.trigger('kernel_starting.Kernel', {kernel: this});
958 var that = this;
957 var that = this;
959 this.kernel_info(function (reply) {
958 this.kernel_info(function (reply) {
960 that.info_reply = reply.content;
959 that.info_reply = reply.content;
961 that.events.trigger('kernel_ready.Kernel', {kernel: that});
960 that.events.trigger('kernel_ready.Kernel', {kernel: that});
962 });
961 });
963
962
964 } else if (execution_state === 'restarting') {
963 } else if (execution_state === 'restarting') {
965 // autorestarting is distinct from restarting,
964 // autorestarting is distinct from restarting,
966 // in that it means the kernel died and the server is restarting it.
965 // in that it means the kernel died and the server is restarting it.
967 // kernel_restarting sets the notification widget,
966 // kernel_restarting sets the notification widget,
968 // autorestart shows the more prominent dialog.
967 // autorestart shows the more prominent dialog.
969 this._autorestart_attempt = this._autorestart_attempt + 1;
968 this._autorestart_attempt = this._autorestart_attempt + 1;
970 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
969 this.events.trigger('kernel_restarting.Kernel', {kernel: this});
971 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
970 this.events.trigger('kernel_autorestarting.Kernel', {kernel: this, attempt: this._autorestart_attempt});
972
971
973 } else if (execution_state === 'dead') {
972 } else if (execution_state === 'dead') {
974 this.events.trigger('kernel_dead.Kernel', {kernel: this});
973 this.events.trigger('kernel_dead.Kernel', {kernel: this});
975 this._kernel_dead();
974 this._kernel_dead();
976 }
975 }
977 };
976 };
978
977
979 /**
978 /**
980 * Handle clear_output message
979 * Handle clear_output message
981 *
980 *
982 * @function _handle_clear_output
981 * @function _handle_clear_output
983 */
982 */
984 Kernel.prototype._handle_clear_output = function (msg) {
983 Kernel.prototype._handle_clear_output = function (msg) {
985 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
984 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
986 if (!callbacks || !callbacks.iopub) {
985 if (!callbacks || !callbacks.iopub) {
987 return;
986 return;
988 }
987 }
989 var callback = callbacks.iopub.clear_output;
988 var callback = callbacks.iopub.clear_output;
990 if (callback) {
989 if (callback) {
991 callback(msg);
990 callback(msg);
992 }
991 }
993 };
992 };
994
993
995 /**
994 /**
996 * handle an output message (execute_result, display_data, etc.)
995 * handle an output message (execute_result, display_data, etc.)
997 *
996 *
998 * @function _handle_output_message
997 * @function _handle_output_message
999 */
998 */
1000 Kernel.prototype._handle_output_message = function (msg) {
999 Kernel.prototype._handle_output_message = function (msg) {
1001 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1000 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1002 if (!callbacks || !callbacks.iopub) {
1001 if (!callbacks || !callbacks.iopub) {
1003 if (this.unsolicited_msg_callback) {
1002 // The message came from another client. Let the UI decide what to
1004 // The message came from another client. Let the UI decide what
1003 // do with it.
1005 // to do with it.
1004 this.events.trigger('received_unsolicited_message.Kernel', msg);
1006 this.unsolicited_msg_callback(msg);
1007 }
1008 return;
1005 return;
1009 }
1006 }
1010 var callback = callbacks.iopub.output;
1007 var callback = callbacks.iopub.output;
1011 if (callback) {
1008 if (callback) {
1012 callback(msg);
1009 callback(msg);
1013 }
1010 }
1014 };
1011 };
1015
1012
1016 /**
1013 /**
1017 * Handle an input message (execute_input).
1014 * Handle an input message (execute_input).
1018 *
1015 *
1019 * @function _handle_input message
1016 * @function _handle_input message
1020 */
1017 */
1021 Kernel.prototype._handle_input_message = function (msg) {
1018 Kernel.prototype._handle_input_message = function (msg) {
1022 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1019 var callbacks = this.get_callbacks_for_msg(msg.parent_header.msg_id);
1023 if (!callbacks && this.unsolicited_msg_callback) {
1020 if (!callbacks) {
1024 // The message came from another client. Let the UI decide what to
1021 // The message came from another client. Let the UI decide what to
1025 // do with it.
1022 // do with it.
1026 this.unsolicited_msg_callback(msg);
1023 this.events.trigger('received_unsolicited_message.Kernel', msg);
1027 }
1024 }
1028 };
1025 };
1029
1026
1030 /**
1027 /**
1031 * Dispatch IOPub messages to respective handlers. Each message
1028 * Dispatch IOPub messages to respective handlers. Each message
1032 * type should have a handler.
1029 * type should have a handler.
1033 *
1030 *
1034 * @function _handle_iopub_message
1031 * @function _handle_iopub_message
1035 */
1032 */
1036 Kernel.prototype._handle_iopub_message = function (e) {
1033 Kernel.prototype._handle_iopub_message = function (e) {
1037 serialize.deserialize(e.data, $.proxy(this._finish_iopub_message, this));
1034 serialize.deserialize(e.data, $.proxy(this._finish_iopub_message, this));
1038 };
1035 };
1039
1036
1040
1037
1041 Kernel.prototype._finish_iopub_message = function (msg) {
1038 Kernel.prototype._finish_iopub_message = function (msg) {
1042 var handler = this.get_iopub_handler(msg.header.msg_type);
1039 var handler = this.get_iopub_handler(msg.header.msg_type);
1043 if (handler !== undefined) {
1040 if (handler !== undefined) {
1044 handler(msg);
1041 handler(msg);
1045 }
1042 }
1046 };
1043 };
1047
1044
1048 /**
1045 /**
1049 * @function _handle_input_request
1046 * @function _handle_input_request
1050 */
1047 */
1051 Kernel.prototype._handle_input_request = function (e) {
1048 Kernel.prototype._handle_input_request = function (e) {
1052 serialize.deserialize(e.data, $.proxy(this._finish_input_request, this));
1049 serialize.deserialize(e.data, $.proxy(this._finish_input_request, this));
1053 };
1050 };
1054
1051
1055
1052
1056 Kernel.prototype._finish_input_request = function (request) {
1053 Kernel.prototype._finish_input_request = function (request) {
1057 var header = request.header;
1054 var header = request.header;
1058 var content = request.content;
1055 var content = request.content;
1059 var metadata = request.metadata;
1056 var metadata = request.metadata;
1060 var msg_type = header.msg_type;
1057 var msg_type = header.msg_type;
1061 if (msg_type !== 'input_request') {
1058 if (msg_type !== 'input_request') {
1062 console.log("Invalid input request!", request);
1059 console.log("Invalid input request!", request);
1063 return;
1060 return;
1064 }
1061 }
1065 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1062 var callbacks = this.get_callbacks_for_msg(request.parent_header.msg_id);
1066 if (callbacks) {
1063 if (callbacks) {
1067 if (callbacks.input) {
1064 if (callbacks.input) {
1068 callbacks.input(request);
1065 callbacks.input(request);
1069 }
1066 }
1070 }
1067 }
1071 };
1068 };
1072
1069
1073 // Backwards compatability.
1070 // Backwards compatability.
1074 IPython.Kernel = Kernel;
1071 IPython.Kernel = Kernel;
1075
1072
1076 return {'Kernel': Kernel};
1073 return {'Kernel': Kernel};
1077 });
1074 });
@@ -1,336 +1,333 b''
1 {% extends "page.html" %}
1 {% extends "page.html" %}
2
2
3 {% block stylesheet %}
3 {% block stylesheet %}
4
4
5 {% if mathjax_url %}
5 {% if mathjax_url %}
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
6 <script type="text/javascript" src="{{mathjax_url}}?config=TeX-AMS_HTML-full&delayStartupUntil=configured" charset="utf-8"></script>
7 {% endif %}
7 {% endif %}
8 <script type="text/javascript">
8 <script type="text/javascript">
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
9 // MathJax disabled, set as null to distingish from *missing* MathJax,
10 // where it will be undefined, and should prompt a dialog later.
10 // where it will be undefined, and should prompt a dialog later.
11 window.mathjax_url = "{{mathjax_url}}";
11 window.mathjax_url = "{{mathjax_url}}";
12 </script>
12 </script>
13
13
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
14 <link rel="stylesheet" href="{{ static_url("components/bootstrap-tour/build/css/bootstrap-tour.min.css") }}" type="text/css" />
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
15 <link rel="stylesheet" href="{{ static_url("components/codemirror/lib/codemirror.css") }}">
16
16
17 {{super()}}
17 {{super()}}
18
18
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
19 <link rel="stylesheet" href="{{ static_url("notebook/css/override.css") }}" type="text/css" />
20
20
21 {% endblock %}
21 {% endblock %}
22
22
23 {% block params %}
23 {% block params %}
24
24
25 data-project="{{project}}"
25 data-project="{{project}}"
26 data-base-url="{{base_url}}"
26 data-base-url="{{base_url}}"
27 data-ws-url="{{ws_url}}"
27 data-ws-url="{{ws_url}}"
28 data-notebook-name="{{notebook_name}}"
28 data-notebook-name="{{notebook_name}}"
29 data-notebook-path="{{notebook_path}}"
29 data-notebook-path="{{notebook_path}}"
30 class="notebook_app"
30 class="notebook_app"
31
31
32 {% endblock %}
32 {% endblock %}
33
33
34
34
35 {% block headercontainer %}
35 {% block headercontainer %}
36
36
37
37
38 <span id="save_widget" class="nav pull-left">
38 <span id="save_widget" class="nav pull-left">
39 <span id="notebook_name"></span>
39 <span id="notebook_name"></span>
40 <span id="checkpoint_status"></span>
40 <span id="checkpoint_status"></span>
41 <span id="autosave_status"></span>
41 <span id="autosave_status"></span>
42 </span>
42 </span>
43
43
44 <span id="kernel_selector_widget" class="pull-right dropdown">
44 <span id="kernel_selector_widget" class="pull-right dropdown">
45 <button class="dropdown-toggle" data-toggle="dropdown" type='button' id="current_kernel_spec">
45 <button class="dropdown-toggle" data-toggle="dropdown" type='button' id="current_kernel_spec">
46 <span class='kernel_name'>Kernel</span>
46 <span class='kernel_name'>Kernel</span>
47 <span class="caret"></span>
47 <span class="caret"></span>
48 </button>
48 </button>
49 <ul id="kernel_selector" class="dropdown-menu">
49 <ul id="kernel_selector" class="dropdown-menu">
50 </ul>
50 </ul>
51 </span>
51 </span>
52
52
53 {% endblock headercontainer %}
53 {% endblock headercontainer %}
54
54
55 {% block header %}
55 {% block header %}
56 <div id="menubar-container" class="container">
56 <div id="menubar-container" class="container">
57 <div id="menubar">
57 <div id="menubar">
58 <div id="menus" class="navbar navbar-default" role="navigation">
58 <div id="menus" class="navbar navbar-default" role="navigation">
59 <div class="container-fluid">
59 <div class="container-fluid">
60 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
60 <button type="button" class="btn btn-default navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
61 <i class="fa fa-bars"></i>
61 <i class="fa fa-bars"></i>
62 <span class="navbar-text">Menu</span>
62 <span class="navbar-text">Menu</span>
63 </button>
63 </button>
64 <ul class="nav navbar-nav navbar-right">
64 <ul class="nav navbar-nav navbar-right">
65 <li id="kernel_indicator">
65 <li id="kernel_indicator">
66 <i id="kernel_indicator_icon"></i>
66 <i id="kernel_indicator_icon"></i>
67 </li>
67 </li>
68 <li id="modal_indicator">
68 <li id="modal_indicator">
69 <i id="modal_indicator_icon"></i>
69 <i id="modal_indicator_icon"></i>
70 </li>
70 </li>
71 <li id="notification_area"></li>
71 <li id="notification_area"></li>
72 </ul>
72 </ul>
73 <div class="navbar-collapse collapse">
73 <div class="navbar-collapse collapse">
74 <ul class="nav navbar-nav">
74 <ul class="nav navbar-nav">
75 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
75 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">File</a>
76 <ul id="file_menu" class="dropdown-menu">
76 <ul id="file_menu" class="dropdown-menu">
77 <li id="new_notebook"
77 <li id="new_notebook"
78 title="Make a new notebook (Opens a new window)">
78 title="Make a new notebook (Opens a new window)">
79 <a href="#">New</a></li>
79 <a href="#">New</a></li>
80 <li id="open_notebook"
80 <li id="open_notebook"
81 title="Opens a new window with the Dashboard view">
81 title="Opens a new window with the Dashboard view">
82 <a href="#">Open...</a></li>
82 <a href="#">Open...</a></li>
83 <!-- <hr/> -->
83 <!-- <hr/> -->
84 <li class="divider"></li>
84 <li class="divider"></li>
85 <li id="copy_notebook"
85 <li id="copy_notebook"
86 title="Open a copy of this notebook's contents and start a new kernel">
86 title="Open a copy of this notebook's contents and start a new kernel">
87 <a href="#">Make a Copy...</a></li>
87 <a href="#">Make a Copy...</a></li>
88 <li id="rename_notebook"><a href="#">Rename...</a></li>
88 <li id="rename_notebook"><a href="#">Rename...</a></li>
89 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
89 <li id="save_checkpoint"><a href="#">Save and Checkpoint</a></li>
90 <!-- <hr/> -->
90 <!-- <hr/> -->
91 <li class="divider"></li>
91 <li class="divider"></li>
92 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
92 <li id="restore_checkpoint" class="dropdown-submenu"><a href="#">Revert to Checkpoint</a>
93 <ul class="dropdown-menu">
93 <ul class="dropdown-menu">
94 <li><a href="#"></a></li>
94 <li><a href="#"></a></li>
95 <li><a href="#"></a></li>
95 <li><a href="#"></a></li>
96 <li><a href="#"></a></li>
96 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
97 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
98 <li><a href="#"></a></li>
99 </ul>
99 </ul>
100 </li>
100 </li>
101 <li class="divider"></li>
101 <li class="divider"></li>
102 <li id="print_preview"><a href="#">Print Preview</a></li>
102 <li id="print_preview"><a href="#">Print Preview</a></li>
103 <li class="dropdown-submenu"><a href="#">Download as</a>
103 <li class="dropdown-submenu"><a href="#">Download as</a>
104 <ul class="dropdown-menu">
104 <ul class="dropdown-menu">
105 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
105 <li id="download_ipynb"><a href="#">IPython Notebook (.ipynb)</a></li>
106 <li id="download_script"><a href="#">Script</a></li>
106 <li id="download_script"><a href="#">Script</a></li>
107 <li id="download_html"><a href="#">HTML (.html)</a></li>
107 <li id="download_html"><a href="#">HTML (.html)</a></li>
108 <li id="download_rst"><a href="#">reST (.rst)</a></li>
108 <li id="download_rst"><a href="#">reST (.rst)</a></li>
109 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
109 <li id="download_pdf"><a href="#">PDF (.pdf)</a></li>
110 </ul>
110 </ul>
111 </li>
111 </li>
112 <li class="divider"></li>
112 <li class="divider"></li>
113 <li id="trust_notebook"
113 <li id="trust_notebook"
114 title="Trust the output of this notebook">
114 title="Trust the output of this notebook">
115 <a href="#" >Trust Notebook</a></li>
115 <a href="#" >Trust Notebook</a></li>
116 <li class="divider"></li>
116 <li class="divider"></li>
117 <li id="kill_and_exit"
117 <li id="kill_and_exit"
118 title="Shutdown this notebook's kernel, and close this window">
118 title="Shutdown this notebook's kernel, and close this window">
119 <a href="#" >Close and halt</a></li>
119 <a href="#" >Close and halt</a></li>
120 </ul>
120 </ul>
121 </li>
121 </li>
122 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
122 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Edit</a>
123 <ul id="edit_menu" class="dropdown-menu">
123 <ul id="edit_menu" class="dropdown-menu">
124 <li id="cut_cell"><a href="#">Cut Cell</a></li>
124 <li id="cut_cell"><a href="#">Cut Cell</a></li>
125 <li id="copy_cell"><a href="#">Copy Cell</a></li>
125 <li id="copy_cell"><a href="#">Copy Cell</a></li>
126 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
126 <li id="paste_cell_above" class="disabled"><a href="#">Paste Cell Above</a></li>
127 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
127 <li id="paste_cell_below" class="disabled"><a href="#">Paste Cell Below</a></li>
128 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
128 <li id="paste_cell_replace" class="disabled"><a href="#">Paste Cell &amp; Replace</a></li>
129 <li id="delete_cell"><a href="#">Delete Cell</a></li>
129 <li id="delete_cell"><a href="#">Delete Cell</a></li>
130 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
130 <li id="undelete_cell" class="disabled"><a href="#">Undo Delete Cell</a></li>
131 <li class="divider"></li>
131 <li class="divider"></li>
132 <li id="split_cell"><a href="#">Split Cell</a></li>
132 <li id="split_cell"><a href="#">Split Cell</a></li>
133 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
133 <li id="merge_cell_above"><a href="#">Merge Cell Above</a></li>
134 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
134 <li id="merge_cell_below"><a href="#">Merge Cell Below</a></li>
135 <li class="divider"></li>
135 <li class="divider"></li>
136 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
136 <li id="move_cell_up"><a href="#">Move Cell Up</a></li>
137 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
137 <li id="move_cell_down"><a href="#">Move Cell Down</a></li>
138 <li class="divider"></li>
138 <li class="divider"></li>
139 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
139 <li id="edit_nb_metadata"><a href="#">Edit Notebook Metadata</a></li>
140 </ul>
140 </ul>
141 </li>
141 </li>
142 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
142 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">View</a>
143 <ul id="view_menu" class="dropdown-menu">
143 <ul id="view_menu" class="dropdown-menu">
144 <li id="toggle_header"
144 <li id="toggle_header"
145 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
145 title="Show/Hide the IPython Notebook logo and notebook title (above menu bar)">
146 <a href="#">Toggle Header</a></li>
146 <a href="#">Toggle Header</a></li>
147 <li id="toggle_toolbar"
147 <li id="toggle_toolbar"
148 title="Show/Hide the action icons (below menu bar)">
148 title="Show/Hide the action icons (below menu bar)">
149 <a href="#">Toggle Toolbar</a></li>
149 <a href="#">Toggle Toolbar</a></li>
150 </ul>
150 </ul>
151 </li>
151 </li>
152 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
152 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Insert</a>
153 <ul id="insert_menu" class="dropdown-menu">
153 <ul id="insert_menu" class="dropdown-menu">
154 <li id="insert_cell_above"
154 <li id="insert_cell_above"
155 title="Insert an empty Code cell above the currently active cell">
155 title="Insert an empty Code cell above the currently active cell">
156 <a href="#">Insert Cell Above</a></li>
156 <a href="#">Insert Cell Above</a></li>
157 <li id="insert_cell_below"
157 <li id="insert_cell_below"
158 title="Insert an empty Code cell below the currently active cell">
158 title="Insert an empty Code cell below the currently active cell">
159 <a href="#">Insert Cell Below</a></li>
159 <a href="#">Insert Cell Below</a></li>
160 </ul>
160 </ul>
161 </li>
161 </li>
162 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
162 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Cell</a>
163 <ul id="cell_menu" class="dropdown-menu">
163 <ul id="cell_menu" class="dropdown-menu">
164 <li id="run_cell" title="Run this cell, and move cursor to the next one">
164 <li id="run_cell" title="Run this cell, and move cursor to the next one">
165 <a href="#">Run</a></li>
165 <a href="#">Run</a></li>
166 <li id="run_cell_select_below" title="Run this cell, select below">
166 <li id="run_cell_select_below" title="Run this cell, select below">
167 <a href="#">Run and Select Below</a></li>
167 <a href="#">Run and Select Below</a></li>
168 <li id="run_cell_insert_below" title="Run this cell, insert below">
168 <li id="run_cell_insert_below" title="Run this cell, insert below">
169 <a href="#">Run and Insert Below</a></li>
169 <a href="#">Run and Insert Below</a></li>
170 <li id="run_all_cells" title="Run all cells in the notebook">
170 <li id="run_all_cells" title="Run all cells in the notebook">
171 <a href="#">Run All</a></li>
171 <a href="#">Run All</a></li>
172 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
172 <li id="run_all_cells_above" title="Run all cells above (but not including) this cell">
173 <a href="#">Run All Above</a></li>
173 <a href="#">Run All Above</a></li>
174 <li id="run_all_cells_below" title="Run this cell and all cells below it">
174 <li id="run_all_cells_below" title="Run this cell and all cells below it">
175 <a href="#">Run All Below</a></li>
175 <a href="#">Run All Below</a></li>
176 <li class="divider"></li>
176 <li class="divider"></li>
177 <li id="change_cell_type" class="dropdown-submenu"
177 <li id="change_cell_type" class="dropdown-submenu"
178 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
178 title="All cells in the notebook have a cell type. By default, new cells are created as 'Code' cells">
179 <a href="#">Cell Type</a>
179 <a href="#">Cell Type</a>
180 <ul class="dropdown-menu">
180 <ul class="dropdown-menu">
181 <li id="to_code"
181 <li id="to_code"
182 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
182 title="Contents will be sent to the kernel for execution, and output will display in the footer of cell">
183 <a href="#">Code</a></li>
183 <a href="#">Code</a></li>
184 <li id="to_markdown"
184 <li id="to_markdown"
185 title="Contents will be rendered as HTML and serve as explanatory text">
185 title="Contents will be rendered as HTML and serve as explanatory text">
186 <a href="#">Markdown</a></li>
186 <a href="#">Markdown</a></li>
187 <li id="to_raw"
187 <li id="to_raw"
188 title="Contents will pass through nbconvert unmodified">
188 title="Contents will pass through nbconvert unmodified">
189 <a href="#">Raw NBConvert</a></li>
189 <a href="#">Raw NBConvert</a></li>
190 </ul>
190 </ul>
191 </li>
191 </li>
192 <li class="divider"></li>
192 <li class="divider"></li>
193 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
193 <li id="current_outputs" class="dropdown-submenu"><a href="#">Current Output</a>
194 <ul class="dropdown-menu">
194 <ul class="dropdown-menu">
195 <li id="toggle_current_output"
195 <li id="toggle_current_output"
196 title="Hide/Show the output of the current cell">
196 title="Hide/Show the output of the current cell">
197 <a href="#">Toggle</a>
197 <a href="#">Toggle</a>
198 </li>
198 </li>
199 <li id="toggle_current_output_scroll"
199 <li id="toggle_current_output_scroll"
200 title="Scroll the output of the current cell">
200 title="Scroll the output of the current cell">
201 <a href="#">Toggle Scrolling</a>
201 <a href="#">Toggle Scrolling</a>
202 </li>
202 </li>
203 <li id="clear_current_output"
203 <li id="clear_current_output"
204 title="Clear the output of the current cell">
204 title="Clear the output of the current cell">
205 <a href="#">Clear</a>
205 <a href="#">Clear</a>
206 </li>
206 </li>
207 </ul>
207 </ul>
208 </li>
208 </li>
209 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
209 <li id="all_outputs" class="dropdown-submenu"><a href="#">All Output</a>
210 <ul class="dropdown-menu">
210 <ul class="dropdown-menu">
211 <li id="toggle_all_output"
211 <li id="toggle_all_output"
212 title="Hide/Show the output of all cells">
212 title="Hide/Show the output of all cells">
213 <a href="#">Toggle</a>
213 <a href="#">Toggle</a>
214 </li>
214 </li>
215 <li id="toggle_all_output_scroll"
215 <li id="toggle_all_output_scroll"
216 title="Scroll the output of all cells">
216 title="Scroll the output of all cells">
217 <a href="#">Toggle Scrolling</a>
217 <a href="#">Toggle Scrolling</a>
218 </li>
218 </li>
219 <li id="clear_all_output"
219 <li id="clear_all_output"
220 title="Clear the output of all cells">
220 title="Clear the output of all cells">
221 <a href="#">Clear</a>
221 <a href="#">Clear</a>
222 </li>
222 </li>
223 </ul>
223 </ul>
224 </li>
224 </li>
225 </ul>
225 </ul>
226 </li>
226 </li>
227 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
227 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Kernel</a>
228 <ul id="kernel_menu" class="dropdown-menu">
228 <ul id="kernel_menu" class="dropdown-menu">
229 <li id="toggle_unsolicited"
230 title="Toggles display of messages from clients other than this one sharing the same kernel.">
231 <a href="#">Show/ignore output from other clients.</a></li>
232 <li id="int_kernel"
229 <li id="int_kernel"
233 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
230 title="Send KeyboardInterrupt (CTRL-C) to the Kernel">
234 <a href="#">Interrupt</a>
231 <a href="#">Interrupt</a>
235 </li>
232 </li>
236 <li id="restart_kernel"
233 <li id="restart_kernel"
237 title="Restart the Kernel">
234 title="Restart the Kernel">
238 <a href="#">Restart</a>
235 <a href="#">Restart</a>
239 </li>
236 </li>
240 <li id="reconnect_kernel"
237 <li id="reconnect_kernel"
241 title="Reconnect to the Kernel">
238 title="Reconnect to the Kernel">
242 <a href="#">Reconnect</a>
239 <a href="#">Reconnect</a>
243 </li>
240 </li>
244 <li class="divider"></li>
241 <li class="divider"></li>
245 <li id="menu-change-kernel" class="dropdown-submenu">
242 <li id="menu-change-kernel" class="dropdown-submenu">
246 <a href="#">Change kernel</a>
243 <a href="#">Change kernel</a>
247 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
244 <ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
248 </li>
245 </li>
249 </ul>
246 </ul>
250 </li>
247 </li>
251 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
248 <li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>
252 <ul id="help_menu" class="dropdown-menu">
249 <ul id="help_menu" class="dropdown-menu">
253 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
250 <li id="notebook_tour" title="A quick tour of the notebook user interface"><a href="#">User Interface Tour</a></li>
254 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
251 <li id="keyboard_shortcuts" title="Opens a tooltip with all keyboard shortcuts"><a href="#">Keyboard Shortcuts</a></li>
255 <li class="divider"></li>
252 <li class="divider"></li>
256 {% set
253 {% set
257 sections = (
254 sections = (
258 (
255 (
259 ("http://ipython.org/documentation.html","IPython Help",True),
256 ("http://ipython.org/documentation.html","IPython Help",True),
260 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
257 ("http://nbviewer.ipython.org/github/ipython/ipython/tree/2.x/examples/Index.ipynb", "Notebook Help", True),
261 ),(
258 ),(
262 ("http://docs.python.org","Python",True),
259 ("http://docs.python.org","Python",True),
263 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
260 ("http://help.github.com/articles/github-flavored-markdown","Markdown",True),
264 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
261 ("http://docs.scipy.org/doc/numpy/reference/","NumPy",True),
265 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
262 ("http://docs.scipy.org/doc/scipy/reference/","SciPy",True),
266 ("http://matplotlib.org/contents.html","Matplotlib",True),
263 ("http://matplotlib.org/contents.html","Matplotlib",True),
267 ("http://docs.sympy.org/latest/index.html","SymPy",True),
264 ("http://docs.sympy.org/latest/index.html","SymPy",True),
268 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
265 ("http://pandas.pydata.org/pandas-docs/stable/","pandas", True)
269 )
266 )
270 )
267 )
271 %}
268 %}
272
269
273 {% for helplinks in sections %}
270 {% for helplinks in sections %}
274 {% for link in helplinks %}
271 {% for link in helplinks %}
275 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
272 <li><a href="{{link[0]}}" {{'target="_blank" title="Opens in a new window"' if link[2]}}>
276 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
273 {{'<i class="fa fa-external-link menu-icon pull-right"></i>' if link[2]}}
277 {{link[1]}}
274 {{link[1]}}
278 </a></li>
275 </a></li>
279 {% endfor %}
276 {% endfor %}
280 {% if not loop.last %}
277 {% if not loop.last %}
281 <li class="divider"></li>
278 <li class="divider"></li>
282 {% endif %}
279 {% endif %}
283 {% endfor %}
280 {% endfor %}
284 <li class="divider"></li>
281 <li class="divider"></li>
285 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
282 <li title="About IPython Notebook"><a id="notebook_about" href="#">About</a></li>
286 </ul>
283 </ul>
287 </li>
284 </li>
288 </ul>
285 </ul>
289 </div>
286 </div>
290 </div>
287 </div>
291 </div>
288 </div>
292 </div>
289 </div>
293
290
294 <div id="maintoolbar" class="navbar">
291 <div id="maintoolbar" class="navbar">
295 <div class="toolbar-inner navbar-inner navbar-nobg">
292 <div class="toolbar-inner navbar-inner navbar-nobg">
296 <div id="maintoolbar-container" class="container"></div>
293 <div id="maintoolbar-container" class="container"></div>
297 </div>
294 </div>
298 </div>
295 </div>
299 </div>
296 </div>
300
297
301 <div class="lower-header-bar"></div>
298 <div class="lower-header-bar"></div>
302 {% endblock header %}
299 {% endblock header %}
303
300
304 {% block site %}
301 {% block site %}
305
302
306
303
307 <div id="ipython-main-app">
304 <div id="ipython-main-app">
308 <div id="notebook_panel">
305 <div id="notebook_panel">
309 <div id="notebook"></div>
306 <div id="notebook"></div>
310 </div>
307 </div>
311 </div>
308 </div>
312
309
313 <div id="pager">
310 <div id="pager">
314 <div id="pager-contents">
311 <div id="pager-contents">
315 <div id="pager-container" class="container"></div>
312 <div id="pager-container" class="container"></div>
316 </div>
313 </div>
317 <div id='pager-button-area'></div>
314 <div id='pager-button-area'></div>
318 </div>
315 </div>
319
316
320 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
317 <div id='tooltip' class='ipython_tooltip' style='display:none'></div>
321
318
322
319
323 {% endblock %}
320 {% endblock %}
324
321
325
322
326 {% block script %}
323 {% block script %}
327 {{super()}}
324 {{super()}}
328 <script type="text/javascript">
325 <script type="text/javascript">
329 sys_info = {{sys_info}};
326 sys_info = {{sys_info}};
330 </script>
327 </script>
331
328
332 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
329 <script src="{{ static_url("components/text-encoding/lib/encoding.js") }}" charset="utf-8"></script>
333
330
334 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
331 <script src="{{ static_url("notebook/js/main.js") }}" charset="utf-8"></script>
335
332
336 {% endblock %}
333 {% endblock %}
General Comments 0
You need to be logged in to leave comments. Login now