##// END OF EJS Templates
Backport PR #8203: autosave & reload on kernelspec switch...
Min RK -
Show More
@@ -1,325 +1,335 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'jquery',
5 'jquery',
6 'base/js/namespace',
6 'base/js/namespace',
7 'base/js/dialog',
7 'base/js/dialog',
8 'base/js/utils',
8 'base/js/utils',
9 ], function($, IPython, dialog, utils) {
9 ], function($, IPython, dialog, utils) {
10 "use strict";
10 "use strict";
11
11
12 var KernelSelector = function(selector, notebook) {
12 var KernelSelector = function(selector, notebook) {
13 var that = this;
13 var that = this;
14 this.selector = selector;
14 this.selector = selector;
15 this.notebook = notebook;
15 this.notebook = notebook;
16 this.notebook.set_kernelselector(this);
16 this.notebook.set_kernelselector(this);
17 this.events = notebook.events;
17 this.events = notebook.events;
18 this.current_selection = null;
18 this.current_selection = null;
19 this.kernelspecs = {};
19 this.kernelspecs = {};
20 if (this.selector !== undefined) {
20 if (this.selector !== undefined) {
21 this.element = $(selector);
21 this.element = $(selector);
22 this.request_kernelspecs();
22 this.request_kernelspecs();
23 }
23 }
24 this.bind_events();
24 this.bind_events();
25 // Make the object globally available for user convenience & inspection
25 // Make the object globally available for user convenience & inspection
26 IPython.kernelselector = this;
26 IPython.kernelselector = this;
27 this._finish_load = null;
27 this._finish_load = null;
28 this._loaded = false;
28 this._loaded = false;
29 this.loaded = new Promise(function(resolve) {
29 this.loaded = new Promise(function(resolve) {
30 that._finish_load = resolve;
30 that._finish_load = resolve;
31 });
31 });
32
32
33 Object.seal(this);
33 Object.seal(this);
34 };
34 };
35
35
36 KernelSelector.prototype.request_kernelspecs = function() {
36 KernelSelector.prototype.request_kernelspecs = function() {
37 var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
37 var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
38 utils.promising_ajax(url).then($.proxy(this._got_kernelspecs, this));
38 utils.promising_ajax(url).then($.proxy(this._got_kernelspecs, this));
39 };
39 };
40
40
41 var _sorted_names = function(kernelspecs) {
41 var _sorted_names = function(kernelspecs) {
42 // sort kernel names
42 // sort kernel names
43 return Object.keys(kernelspecs).sort(function (a, b) {
43 return Object.keys(kernelspecs).sort(function (a, b) {
44 // sort by display_name
44 // sort by display_name
45 var da = kernelspecs[a].spec.display_name;
45 var da = kernelspecs[a].spec.display_name;
46 var db = kernelspecs[b].spec.display_name;
46 var db = kernelspecs[b].spec.display_name;
47 if (da === db) {
47 if (da === db) {
48 return 0;
48 return 0;
49 } else if (da > db) {
49 } else if (da > db) {
50 return 1;
50 return 1;
51 } else {
51 } else {
52 return -1;
52 return -1;
53 }
53 }
54 });
54 });
55 };
55 };
56
56
57 KernelSelector.prototype._got_kernelspecs = function(data) {
57 KernelSelector.prototype._got_kernelspecs = function(data) {
58 var that = this;
58 var that = this;
59 this.kernelspecs = data.kernelspecs;
59 this.kernelspecs = data.kernelspecs;
60 var change_kernel_submenu = $("#menu-change-kernel-submenu");
60 var change_kernel_submenu = $("#menu-change-kernel-submenu");
61 var new_notebook_submenu = $("#menu-new-notebook-submenu");
61 var new_notebook_submenu = $("#menu-new-notebook-submenu");
62 var keys = _sorted_names(data.kernelspecs);
62 var keys = _sorted_names(data.kernelspecs);
63
63
64 keys.map(function (key) {
64 keys.map(function (key) {
65 // Create the Kernel > Change kernel submenu
65 // Create the Kernel > Change kernel submenu
66 var ks = data.kernelspecs[key];
66 var ks = data.kernelspecs[key];
67 change_kernel_submenu.append(
67 change_kernel_submenu.append(
68 $("<li>").attr("id", "kernel-submenu-"+ks.name).append(
68 $("<li>").attr("id", "kernel-submenu-"+ks.name).append(
69 $('<a>')
69 $('<a>')
70 .attr('href', '#')
70 .attr('href', '#')
71 .click( function () {
71 .click( function () {
72 that.set_kernel(ks.name);
72 that.set_kernel(ks.name);
73 })
73 })
74 .text(ks.spec.display_name)
74 .text(ks.spec.display_name)
75 )
75 )
76 );
76 );
77 // Create the File > New Notebook submenu
77 // Create the File > New Notebook submenu
78 new_notebook_submenu.append(
78 new_notebook_submenu.append(
79 $("<li>").attr("id", "new-notebook-submenu-"+ks.name).append(
79 $("<li>").attr("id", "new-notebook-submenu-"+ks.name).append(
80 $('<a>')
80 $('<a>')
81 .attr('href', '#')
81 .attr('href', '#')
82 .click( function () {
82 .click( function () {
83 that.new_notebook(ks.name);
83 that.new_notebook(ks.name);
84 })
84 })
85 .text(ks.spec.display_name)
85 .text(ks.spec.display_name)
86 )
86 )
87 );
87 );
88
88
89 });
89 });
90 // trigger loaded promise
90 // trigger loaded promise
91 this._loaded = true;
91 this._loaded = true;
92 this._finish_load();
92 this._finish_load();
93 };
93 };
94
94
95 KernelSelector.prototype._spec_changed = function (event, ks) {
95 KernelSelector.prototype._spec_changed = function (event, ks) {
96 /** event handler for spec_changed */
96 /** event handler for spec_changed */
97 var that = this;
97
98
98 // update selection
99 // update selection
99 this.current_selection = ks.name;
100 this.current_selection = ks.name;
100
101
101 // put the current kernel at the top of File > New Notebook
102 // put the current kernel at the top of File > New Notebook
102 var cur_kernel_entry = $("#new-notebook-submenu-" + ks.name);
103 var cur_kernel_entry = $("#new-notebook-submenu-" + ks.name);
103 var parent = cur_kernel_entry.parent();
104 var parent = cur_kernel_entry.parent();
104 // do something only if there is more than one kernel
105 // do something only if there is more than one kernel
105 if (parent.children().length > 1) {
106 if (parent.children().length > 1) {
106 // first, sort back the submenu
107 // first, sort back the submenu
107 parent.append(
108 parent.append(
108 parent.children("li[class!='divider']").sort(
109 parent.children("li[class!='divider']").sort(
109 function (a,b) {
110 function (a,b) {
110 var da = $("a",a).text();
111 var da = $("a",a).text();
111 var db = $("a",b).text();
112 var db = $("a",b).text();
112 if (da === db) {
113 if (da === db) {
113 return 0;
114 return 0;
114 } else if (da > db) {
115 } else if (da > db) {
115 return 1;
116 return 1;
116 } else {
117 } else {
117 return -1;
118 return -1;
118 }}));
119 }}));
119 // then, if there is no divider yet, add one
120 // then, if there is no divider yet, add one
120 if (!parent.children("li[class='divider']").length) {
121 if (!parent.children("li[class='divider']").length) {
121 parent.prepend($("<li>").attr("class","divider"));
122 parent.prepend($("<li>").attr("class","divider"));
122 }
123 }
123 // finally, put the current kernel at the top
124 // finally, put the current kernel at the top
124 parent.prepend(cur_kernel_entry);
125 parent.prepend(cur_kernel_entry);
125 }
126 }
126
127
127 // load logo
128 // load logo
128 var logo_img = this.element.find("img.current_kernel_logo");
129 var logo_img = this.element.find("img.current_kernel_logo");
129 $("#kernel_indicator").find('.kernel_indicator_name').text(ks.spec.display_name);
130 $("#kernel_indicator").find('.kernel_indicator_name').text(ks.spec.display_name);
130 if (ks.resources['logo-64x64']) {
131 if (ks.resources['logo-64x64']) {
131 logo_img.attr("src", ks.resources['logo-64x64']);
132 logo_img.attr("src", ks.resources['logo-64x64']);
132 logo_img.show();
133 logo_img.show();
133 } else {
134 } else {
134 logo_img.hide();
135 logo_img.hide();
135 }
136 }
136
137
137 // load kernel css
138 // load kernel css
138 var css_url = ks.resources['kernel.css'];
139 var css_url = ks.resources['kernel.css'];
139 if (css_url) {
140 if (css_url) {
140 $('#kernel-css').attr('href', css_url);
141 $('#kernel-css').attr('href', css_url);
141 } else {
142 } else {
142 $('#kernel-css').attr('href', '');
143 $('#kernel-css').attr('href', '');
143 }
144 }
144
145
145 // load kernel js
146 // load kernel js
146 if (ks.resources['kernel.js']) {
147 if (ks.resources['kernel.js']) {
147 require([ks.resources['kernel.js']],
148 require([ks.resources['kernel.js']],
148 function (kernel_mod) {
149 function (kernel_mod) {
149 if (kernel_mod && kernel_mod.onload) {
150 if (kernel_mod && kernel_mod.onload) {
150 kernel_mod.onload();
151 kernel_mod.onload();
151 } else {
152 } else {
152 console.warn("Kernel " + ks.name + " has a kernel.js file that does not contain "+
153 console.warn("Kernel " + ks.name + " has a kernel.js file that does not contain "+
153 "any asynchronous module definition. This is undefined behavior "+
154 "any asynchronous module definition. This is undefined behavior "+
154 "and not recommended.");
155 "and not recommended.");
155 }
156 }
156 }, function (err) {
157 }, function (err) {
157 console.warn("Failed to load kernel.js from ", ks.resources['kernel.js'], err);
158 console.warn("Failed to load kernel.js from ", ks.resources['kernel.js'], err);
158 }
159 }
159 );
160 );
161 this.events.on('spec_changed.Kernel', function (evt, new_ks) {
162 if (ks.name != new_ks.name) {
163 console.warn("kernelspec %s had custom kernel.js. Forcing page reload for %s.",
164 ks.name, new_ks.name);
165 that.notebook.save_notebook().then(function () {
166 window.location.reload();
167 });
168 }
169 });
160 }
170 }
161 };
171 };
162
172
163 KernelSelector.prototype.set_kernel = function (selected) {
173 KernelSelector.prototype.set_kernel = function (selected) {
164 /** set the kernel by name, ensuring kernelspecs have been loaded, first
174 /** set the kernel by name, ensuring kernelspecs have been loaded, first
165
175
166 kernel can be just a kernel name, or a notebook kernelspec metadata
176 kernel can be just a kernel name, or a notebook kernelspec metadata
167 (name, language, display_name).
177 (name, language, display_name).
168 */
178 */
169 var that = this;
179 var that = this;
170 if (typeof selected === 'string') {
180 if (typeof selected === 'string') {
171 selected = {
181 selected = {
172 name: selected
182 name: selected
173 };
183 };
174 }
184 }
175 if (this._loaded) {
185 if (this._loaded) {
176 this._set_kernel(selected);
186 this._set_kernel(selected);
177 } else {
187 } else {
178 return this.loaded.then(function () {
188 return this.loaded.then(function () {
179 that._set_kernel(selected);
189 that._set_kernel(selected);
180 });
190 });
181 }
191 }
182 };
192 };
183
193
184 KernelSelector.prototype._set_kernel = function (selected) {
194 KernelSelector.prototype._set_kernel = function (selected) {
185 /** Actually set the kernel (kernelspecs have been loaded) */
195 /** Actually set the kernel (kernelspecs have been loaded) */
186 if (selected.name === this.current_selection) {
196 if (selected.name === this.current_selection) {
187 // only trigger event if value changed
197 // only trigger event if value changed
188 return;
198 return;
189 }
199 }
190 var kernelspecs = this.kernelspecs;
200 var kernelspecs = this.kernelspecs;
191 var ks = kernelspecs[selected.name];
201 var ks = kernelspecs[selected.name];
192 if (ks === undefined) {
202 if (ks === undefined) {
193 var available = _sorted_names(kernelspecs);
203 var available = _sorted_names(kernelspecs);
194 var matches = [];
204 var matches = [];
195 if (selected.language && selected.language.length > 0) {
205 if (selected.language && selected.language.length > 0) {
196 available.map(function (name) {
206 available.map(function (name) {
197 if (kernelspecs[name].spec.language.toLowerCase() === selected.language.toLowerCase()) {
207 if (kernelspecs[name].spec.language.toLowerCase() === selected.language.toLowerCase()) {
198 matches.push(name);
208 matches.push(name);
199 }
209 }
200 });
210 });
201 }
211 }
202 if (matches.length === 1) {
212 if (matches.length === 1) {
203 ks = kernelspecs[matches[0]];
213 ks = kernelspecs[matches[0]];
204 console.log("No exact match found for " + selected.name +
214 console.log("No exact match found for " + selected.name +
205 ", using only kernel that matches language=" + selected.language, ks);
215 ", using only kernel that matches language=" + selected.language, ks);
206 this.events.trigger("spec_match_found.Kernel", {
216 this.events.trigger("spec_match_found.Kernel", {
207 selected: selected,
217 selected: selected,
208 found: ks,
218 found: ks,
209 });
219 });
210 }
220 }
211 // if still undefined, trigger failure event
221 // if still undefined, trigger failure event
212 if (ks === undefined) {
222 if (ks === undefined) {
213 this.events.trigger("spec_not_found.Kernel", {
223 this.events.trigger("spec_not_found.Kernel", {
214 selected: selected,
224 selected: selected,
215 matches: matches,
225 matches: matches,
216 available: available,
226 available: available,
217 });
227 });
218 return;
228 return;
219 }
229 }
220 }
230 }
221 if (this.notebook._session_starting &&
231 if (this.notebook._session_starting &&
222 this.notebook.session.kernel.name !== ks.name) {
232 this.notebook.session.kernel.name !== ks.name) {
223 console.error("Cannot change kernel while waiting for pending session start.");
233 console.error("Cannot change kernel while waiting for pending session start.");
224 return;
234 return;
225 }
235 }
226 this.current_selection = ks.name;
236 this.current_selection = ks.name;
227 this.events.trigger('spec_changed.Kernel', ks);
237 this.events.trigger('spec_changed.Kernel', ks);
228 };
238 };
229
239
230 KernelSelector.prototype._spec_not_found = function (event, data) {
240 KernelSelector.prototype._spec_not_found = function (event, data) {
231 var that = this;
241 var that = this;
232 var select = $("<select>").addClass('form-control');
242 var select = $("<select>").addClass('form-control');
233 console.warn("Kernelspec not found:", data);
243 console.warn("Kernelspec not found:", data);
234 var names;
244 var names;
235 if (data.matches.length > 1) {
245 if (data.matches.length > 1) {
236 names = data.matches;
246 names = data.matches;
237 } else {
247 } else {
238 names = data.available;
248 names = data.available;
239 }
249 }
240 names.map(function (name) {
250 names.map(function (name) {
241 var ks = that.kernelspecs[name];
251 var ks = that.kernelspecs[name];
242 select.append(
252 select.append(
243 $('<option/>').attr('value', ks.name).text(ks.spec.display_name || ks.name)
253 $('<option/>').attr('value', ks.name).text(ks.spec.display_name || ks.name)
244 );
254 );
245 });
255 });
246
256
247 var body = $("<form>").addClass("form-inline").append(
257 var body = $("<form>").addClass("form-inline").append(
248 $("<span>").text(
258 $("<span>").text(
249 "I couldn't find a kernel matching " + (data.selected.display_name || data.selected.name) + "." +
259 "I couldn't find a kernel matching " + (data.selected.display_name || data.selected.name) + "." +
250 " Please select a kernel:"
260 " Please select a kernel:"
251 )
261 )
252 ).append(select);
262 ).append(select);
253
263
254 dialog.modal({
264 dialog.modal({
255 title : 'Kernel not found',
265 title : 'Kernel not found',
256 body : body,
266 body : body,
257 buttons : {
267 buttons : {
258 'Continue without kernel' : {
268 'Continue without kernel' : {
259 class : 'btn-danger',
269 class : 'btn-danger',
260 click : function () {
270 click : function () {
261 that.events.trigger('no_kernel.Kernel');
271 that.events.trigger('no_kernel.Kernel');
262 }
272 }
263 },
273 },
264 OK : {
274 OK : {
265 class : 'btn-primary',
275 class : 'btn-primary',
266 click : function () {
276 click : function () {
267 that.set_kernel(select.val());
277 that.set_kernel(select.val());
268 }
278 }
269 }
279 }
270 }
280 }
271 });
281 });
272 };
282 };
273
283
274 KernelSelector.prototype.new_notebook = function (kernel_name) {
284 KernelSelector.prototype.new_notebook = function (kernel_name) {
275
285
276 var w = window.open('', IPython._target);
286 var w = window.open('', IPython._target);
277 // Create a new notebook in the same path as the current
287 // Create a new notebook in the same path as the current
278 // notebook's path.
288 // notebook's path.
279 var that = this;
289 var that = this;
280 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
290 var parent = utils.url_path_split(that.notebook.notebook_path)[0];
281 that.notebook.contents.new_untitled(parent, {type: "notebook"}).then(
291 that.notebook.contents.new_untitled(parent, {type: "notebook"}).then(
282 function (data) {
292 function (data) {
283 var url = utils.url_join_encode(
293 var url = utils.url_join_encode(
284 that.notebook.base_url, 'notebooks', data.path
294 that.notebook.base_url, 'notebooks', data.path
285 );
295 );
286 url += "?kernel_name=" + kernel_name;
296 url += "?kernel_name=" + kernel_name;
287 w.location = url;
297 w.location = url;
288 },
298 },
289 function(error) {
299 function(error) {
290 w.close();
300 w.close();
291 dialog.modal({
301 dialog.modal({
292 title : 'Creating Notebook Failed',
302 title : 'Creating Notebook Failed',
293 body : "The error was: " + error.message,
303 body : "The error was: " + error.message,
294 buttons : {'OK' : {'class' : 'btn-primary'}}
304 buttons : {'OK' : {'class' : 'btn-primary'}}
295 });
305 });
296 }
306 }
297 );
307 );
298 };
308 };
299
309
300 KernelSelector.prototype.lock_switch = function() {
310 KernelSelector.prototype.lock_switch = function() {
301 // should set a flag and display warning+reload if user want to
311 // should set a flag and display warning+reload if user want to
302 // re-change kernel. As UI discussion never finish
312 // re-change kernel. As UI discussion never finish
303 // making that a separate PR.
313 // making that a separate PR.
304 console.warn('switching kernel is not guaranteed to work !');
314 console.warn('switching kernel is not guaranteed to work !');
305 };
315 };
306
316
307 KernelSelector.prototype.bind_events = function() {
317 KernelSelector.prototype.bind_events = function() {
308 var that = this;
318 var that = this;
309 this.events.on('spec_changed.Kernel', $.proxy(this._spec_changed, this));
319 this.events.on('spec_changed.Kernel', $.proxy(this._spec_changed, this));
310 this.events.on('spec_not_found.Kernel', $.proxy(this._spec_not_found, this));
320 this.events.on('spec_not_found.Kernel', $.proxy(this._spec_not_found, this));
311 this.events.on('kernel_created.Session', function (event, data) {
321 this.events.on('kernel_created.Session', function (event, data) {
312 that.set_kernel(data.kernel.name);
322 that.set_kernel(data.kernel.name);
313 });
323 });
314
324
315 var logo_img = this.element.find("img.current_kernel_logo");
325 var logo_img = this.element.find("img.current_kernel_logo");
316 logo_img.on("load", function() {
326 logo_img.on("load", function() {
317 logo_img.show();
327 logo_img.show();
318 });
328 });
319 logo_img.on("error", function() {
329 logo_img.on("error", function() {
320 logo_img.hide();
330 logo_img.hide();
321 });
331 });
322 };
332 };
323
333
324 return {'KernelSelector': KernelSelector};
334 return {'KernelSelector': KernelSelector};
325 });
335 });
General Comments 0
You need to be logged in to leave comments. Login now