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