Merge pull request #6126 from takluyver/kernelspecs-nb-ui

UI for changing kernels in the notebook
This commit is contained in:
Thomas Kluyver 2014-07-28 11:33:14 -07:00
commit c9631845fa
9 changed files with 301 additions and 5 deletions

View File

@ -7,6 +7,8 @@ from tornado import web
from ...base.handlers import IPythonHandler, json_errors
from IPython.kernel.kernelspec import _pythonfirst
class MainKernelSpecHandler(IPythonHandler):
SUPPORTED_METHODS = ('GET',)
@ -16,7 +18,7 @@ class MainKernelSpecHandler(IPythonHandler):
def get(self):
ksm = self.kernel_spec_manager
results = []
for kernel_name in ksm.find_kernel_specs():
for kernel_name in sorted(ksm.find_kernel_specs(), key=_pythonfirst):
d = ksm.get_kernel_spec(kernel_name).to_dict()
d['name'] = kernel_name
results.append(d)

View File

@ -0,0 +1,81 @@
// Copyright (c) IPython Development Team.
// Distributed under the terms of the Modified BSD License.
define([
'base/js/namespace',
'jquery',
'base/js/utils',
], function(IPython, $, utils) {
"use strict";
var KernelSelector = function(selector, notebook) {
this.selector = selector;
this.notebook = notebook;
this.events = notebook.events;
this.current_selection = notebook.default_kernel_name;
this.kernelspecs = {};
if (this.selector !== undefined) {
this.element = $(selector);
this.request_kernelspecs();
}
this.bind_events();
// Make the object globally available for user convenience & inspection
IPython.kernelselector = this;
};
KernelSelector.prototype.request_kernelspecs = function() {
var url = utils.url_join_encode(this.notebook.base_url, 'api/kernelspecs');
$.ajax(url, {success: $.proxy(this._got_kernelspecs, this)});
};
KernelSelector.prototype._got_kernelspecs = function(data, status, xhr) {
this.kernelspecs = {};
var menu = this.element.find("#kernel_selector");
var change_kernel_submenu = $("#menu-change-kernel-submenu");
for (var i = 0; i < data.length; i++) {
var ks = data[i];
this.kernelspecs[ks.name] = ks;
var ksentry = $("<li>").attr("id", "kernel-" +ks.name).append($('<a>')
.attr('href', '#')
.click($.proxy(this.change_kernel, this, ks.name))
.text(ks.display_name));
menu.append(ksentry);
var ks_submenu_entry = $("<li>").attr("id", "kernel-submenu-"+ks.name).append($('<a>')
.attr('href', '#')
.click($.proxy(this.change_kernel, this, ks.name))
.text(ks.display_name));
change_kernel_submenu.append(ks_submenu_entry);
}
};
KernelSelector.prototype.change_kernel = function(kernel_name) {
if (kernel_name === this.current_selection) {
return;
}
var ks = this.kernelspecs[kernel_name];
this.events.trigger('spec_changed.Kernel', ks);
this.notebook.session.delete();
this.notebook.start_session(kernel_name);
};
KernelSelector.prototype.bind_events = function() {
var that = this;
this.events.on('spec_changed.Kernel', function(event, data) {
that.current_selection = data.name;
that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
});
this.events.on('started.Session', function(events, session) {
if (session.kernel_name !== that.current_selection) {
// If we created a 'python' session, we only know if it's Python
// 3 or 2 on the server's reply, so we fire the event again to
// set things up.
var ks = that.kernelspecs[session.kernel_name];
that.events.trigger('spec_changed.Kernel', ks);
}
});
};
return {'KernelSelector': KernelSelector};
});

View File

@ -18,6 +18,7 @@ require([
'notebook/js/savewidget',
'notebook/js/keyboardmanager',
'notebook/js/config',
'notebook/js/kernelselector',
// only loaded, not used:
'custom/custom',
], function(
@ -36,7 +37,8 @@ require([
notificationarea,
savewidget,
keyboardmanager,
config
config,
kernelselector
) {
"use strict";
@ -90,6 +92,8 @@ require([
notebook: notebook,
keyboard_manager: keyboard_manager});
notification_area.init_notification_widgets();
var kernel_selector = new kernelselector.KernelSelector(
'#kernel_selector_widget', notebook);
$('body').append('<div id="fonttest"><pre><span id="test1">x</span>'+
'<span id="test2" style="font-weight: bold;">x</span>'+

View File

@ -121,6 +121,7 @@ define([
this.notebook_name_blacklist_re = /[\/\\:]/;
this.nbformat = 3; // Increment this when changing the nbformat
this.nbformat_minor = 0; // Increment this when changing the nbformat
this.codemirror_mode = 'ipython'
this.style();
this.create_elements();
this.bind_events();
@ -214,6 +215,13 @@ define([
}
});
});
this.events.on('spec_changed.Kernel', function(event, data) {
that.set_kernelspec_metadata(data);
if (data.codemirror_mode) {
that.set_codemirror_mode(data.codemirror_mode);
}
});
var collapse_time = function (time) {
var app_height = $('#ipython-main-app').height(); // content height
@ -336,6 +344,16 @@ define([
notebook: this,
keyboard_manager: this.keyboard_manager});
};
Notebook.prototype.set_kernelspec_metadata = function(ks) {
var tostore = {};
$.map(ks, function(value, field) {
if (field !== 'argv' && field !== 'env') {
tostore[field] = value;
}
});
this.metadata.kernelspec = tostore;
}
// Cell indexing, retrieval, etc.
@ -1488,6 +1506,34 @@ define([
Notebook.prototype.cell_toggle_line_numbers = function() {
this.get_selected_cell().toggle_line_numbers();
};
/**
* Set the codemirror mode for all code cells, including the default for
* new code cells.
*
* @method set_codemirror_mode
*/
Notebook.prototype.set_codemirror_mode = function(newmode){
if (newmode === this.codemirror_mode) {
return;
}
this.codemirror_mode = newmode;
codecell.CodeCell.options_default.cm_config.mode = newmode;
modename = newmode.name || newmode
that = this;
CodeMirror.requireMode(modename, function(){
$.map(that.get_cells(), function(cell, i) {
if (cell.cell_type === 'code'){
cell.code_mirror.setOption('mode', newmode);
// This is currently redundant, because cm_config ends up as
// codemirror's own .options object, but I don't want to
// rely on that.
cell.cm_config.mode = newmode;
}
});
})
};
// Session related things
@ -1496,7 +1542,10 @@ define([
*
* @method start_session
*/
Notebook.prototype.start_session = function () {
Notebook.prototype.start_session = function (kernel_name) {
if (kernel_name === undefined) {
kernel_name = this.default_kernel_name;
}
this.session = new session.Session({
base_url: this.base_url,
ws_url: this.ws_url,
@ -1505,7 +1554,7 @@ define([
// For now, create all sessions with the 'python' kernel, which is the
// default. Later, the user will be able to select kernels. This is
// overridden if KernelManager.kernel_cmd is specified for the server.
kernel_name: this.default_kernel_name,
kernel_name: kernel_name,
notebook: this});
this.session.start($.proxy(this._session_started, this));
@ -1730,6 +1779,13 @@ define([
this.metadata = content.metadata;
this.notebook_name = data.name;
var trusted = true;
// Trigger an event changing the kernel spec - this will set the default
// codemirror mode
if (this.metadata.kernelspec !== undefined) {
this.events.trigger('spec_changed.Kernel', this.metadata.kernelspec);
}
// Only handle 1 worksheet for now.
var worksheet = content.worksheets[0];
if (worksheet !== undefined) {
@ -2216,7 +2272,10 @@ define([
// Create the session after the notebook is completely loaded to prevent
// code execution upon loading, which is a security risk.
if (this.session === null) {
this.start_session();
var kernelspec = this.metadata.kernelspec || {};
var kernel_name = kernelspec.name || this.default_kernel_name;
this.start_session(kernel_name);
}
// load our checkpoint list
this.list_checkpoints();

View File

@ -0,0 +1,14 @@
#kernel_selector_widget {
margin-right: 1em;
float:right;
& > button {
.btn();
.btn-default();
.btn-sm();
& > span.caret {
margin-top:0px;
}
}
}

View File

@ -8,6 +8,7 @@
@import "notebook.less";
@import "celltoolbar.less";
@import "completer.less";
@import "kernelselector.less";
@import "menubar.less";
@import "notificationarea.less";
@import "notificationwidget.less";

View File

@ -13,6 +13,7 @@ define([
this.kernel = null;
this.id = null;
this.notebook = options.notebook;
this.events = options.notebook.events;
this.name = options.notebook_name;
this.path = options.notebook_path;
this.kernel_name = options.kernel_name;
@ -91,6 +92,9 @@ define([
*/
Session.prototype._handle_start_success = function (data, status, xhr) {
this.id = data.id;
// If we asked for 'python', the response will have 'python3' or 'python2'.
this.kernel_name = data.kernel.name;
this.events.trigger('started.Session', this);
var kernel_service_url = utils.url_path_join(this.base_url, "api/kernels");
this.kernel = new kernel.Kernel(kernel_service_url, this.ws_url, this.notebook, this.kernel_name);
this.kernel._kernel_started(data.kernel);

View File

@ -9436,6 +9436,122 @@ p {
.completions select option.context {
color: #3071a9;
}
#kernel_selector_widget {
margin-right: 1em;
float: right;
}
#kernel_selector_widget > button {
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
vertical-align: middle;
cursor: pointer;
background-image: none;
border: 1px solid transparent;
white-space: nowrap;
padding: 6px 12px;
font-size: 13px;
line-height: 1.42857143;
border-radius: 4px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
color: #333333;
background-color: #ffffff;
border-color: #cccccc;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
border-radius: 3px;
}
#kernel_selector_widget > button:focus,
#kernel_selector_widget > button:active:focus,
#kernel_selector_widget > button.active:focus {
outline: thin dotted;
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
#kernel_selector_widget > button:hover,
#kernel_selector_widget > button:focus {
color: #333333;
text-decoration: none;
}
#kernel_selector_widget > button:active,
#kernel_selector_widget > button.active {
outline: 0;
background-image: none;
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
}
#kernel_selector_widget > button.disabled,
#kernel_selector_widget > button[disabled],
fieldset[disabled] #kernel_selector_widget > button {
cursor: not-allowed;
pointer-events: none;
opacity: 0.65;
filter: alpha(opacity=65);
-webkit-box-shadow: none;
box-shadow: none;
}
#kernel_selector_widget > button [class^="icon-"].icon-large,
#kernel_selector_widget > button [class*=" icon-"].icon-large {
line-height: .9em;
}
#kernel_selector_widget > button [class^="icon-"].icon-spin,
#kernel_selector_widget > button [class*=" icon-"].icon-spin {
display: inline-block;
}
#kernel_selector_widget > button [class^="icon-"].pull-left.icon-2x,
#kernel_selector_widget > button [class*=" icon-"].pull-left.icon-2x,
#kernel_selector_widget > button [class^="icon-"].pull-right.icon-2x,
#kernel_selector_widget > button [class*=" icon-"].pull-right.icon-2x {
margin-top: .18em;
}
#kernel_selector_widget > button [class^="icon-"].icon-spin.icon-large,
#kernel_selector_widget > button [class*=" icon-"].icon-spin.icon-large {
line-height: .8em;
}
#kernel_selector_widget > button:hover,
#kernel_selector_widget > button:focus,
#kernel_selector_widget > button:active,
#kernel_selector_widget > button.active,
.open .dropdown-toggle#kernel_selector_widget > button {
color: #333333;
background-color: #ebebeb;
border-color: #adadad;
}
#kernel_selector_widget > button:active,
#kernel_selector_widget > button.active,
.open .dropdown-toggle#kernel_selector_widget > button {
background-image: none;
}
#kernel_selector_widget > button.disabled,
#kernel_selector_widget > button[disabled],
fieldset[disabled] #kernel_selector_widget > button,
#kernel_selector_widget > button.disabled:hover,
#kernel_selector_widget > button[disabled]:hover,
fieldset[disabled] #kernel_selector_widget > button:hover,
#kernel_selector_widget > button.disabled:focus,
#kernel_selector_widget > button[disabled]:focus,
fieldset[disabled] #kernel_selector_widget > button:focus,
#kernel_selector_widget > button.disabled:active,
#kernel_selector_widget > button[disabled]:active,
fieldset[disabled] #kernel_selector_widget > button:active,
#kernel_selector_widget > button.disabled.active,
#kernel_selector_widget > button[disabled].active,
fieldset[disabled] #kernel_selector_widget > button.active {
background-color: #ffffff;
border-color: #cccccc;
}
#kernel_selector_widget > button .badge {
color: #ffffff;
background-color: #333333;
}
#kernel_selector_widget > button > span.caret {
margin-top: 0px;
}
#menubar {
margin-top: 0px;
margin-bottom: -19px;

View File

@ -34,12 +34,22 @@ class="notebook_app"
{% block header %}
<span id="save_widget" class="nav pull-left">
<span id="notebook_name"></span>
<span id="checkpoint_status"></span>
<span id="autosave_status"></span>
</span>
<span id="kernel_selector_widget" class="pull-right dropdown">
<button href="#" class="dropdown-toggle" data-toggle="dropdown" type='button' id="current_kernel_spec">
<span class='kernel_name'>Python</span>
<span class="caret"></span>
</button>
<ul id="kernel_selector" class="dropdown-menu">
</ul>
</span>
{% endblock %}
@ -216,6 +226,11 @@ class="notebook_app"
<li id="restart_kernel"
title="Restart the Kernel">
<a href="#">Restart</a></li>
<li class="divider"></li>
<li id="menu-change-kernel" class="dropdown-submenu">
<a href="#">Change kernel</a>
<ul class="dropdown-menu" id="menu-change-kernel-submenu"></ul>
</li>
</ul>
</li>
<li class="dropdown"><a href="#" class="dropdown-toggle" data-toggle="dropdown">Help</a>