From 41626420e215a2a4a989e51465e1aff476fb9ccf Mon Sep 17 00:00:00 2001 From: Thomas Kluyver Date: Sat, 12 Jul 2014 12:18:21 -0500 Subject: [PATCH 01/17] Allow switching kernel from the notebook UI --- .../html/static/notebook/js/kernelselector.js | 58 +++++++++++++++++++ IPython/html/static/notebook/js/main.js | 6 +- IPython/html/static/notebook/js/notebook.js | 8 ++- IPython/html/templates/notebook.html | 6 ++ 4 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 IPython/html/static/notebook/js/kernelselector.js diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js new file mode 100644 index 000000000..8dcf7142a --- /dev/null +++ b/IPython/html/static/notebook/js/kernelselector.js @@ -0,0 +1,58 @@ +// 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.kernelspecs = {}; + this.current = "python"; + if (this.selector !== undefined) { + this.element = $(selector); + this.style(); + this.request_kernelspecs(); + } + }; + + KernelSelector.prototype.style = function() { + }; + + 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"); + for (var i = 0; i < data.length; i++) { + var ks = data[i]; + this.kernelspecs[ks.name] = ks; + var ksentry = $("
  • ").attr("id", "kernel-" +ks.name).append($('') + .attr('href', '#') + .click($.proxy(this.change_kernel, this, ks.name)) + .text(ks.display_name)); + menu.append(ksentry); + } + }; + + KernelSelector.prototype.change_kernel = function(kernel_name) { + console.log("change_kernel " + kernel_name + " from " + this.current); + if (kernel_name === this.current) { + return; + } + this.notebook.session.delete(); + this.notebook.start_session(kernel_name); + this.current = kernel_name; + var display_name = this.kernelspecs[kernel_name].display_name; + this.element.find("#current_kernel_spec").text(display_name); + }; + + return {'KernelSelector': KernelSelector}; +}); diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js index 78c91bd19..ca945fb32 100644 --- a/IPython/html/static/notebook/js/main.js +++ b/IPython/html/static/notebook/js/main.js @@ -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('
    x'+
                          'x'+
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index d87ab6361..0af6642a8 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -1496,7 +1496,11 @@ 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;
    +        }
    +        console.log("start_session", kernel_name);
             this.session = new session.Session({
                 base_url: this.base_url,
                 ws_url: this.ws_url,
    @@ -1505,7 +1509,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));
    diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html
    index 959621f49..0ad6185af 100644
    --- a/IPython/html/templates/notebook.html
    +++ b/IPython/html/templates/notebook.html
    @@ -40,6 +40,12 @@ class="notebook_app"
         
     
     
    +
    +    Python
    +    
    +
    +
     {% endblock %}
     
     
    
    From 9581bd7dc3e20fa44d83051bbd2d1285cfc5ca3c Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Sat, 12 Jul 2014 14:20:11 -0500
    Subject: [PATCH 02/17] Change displayed kernel name when our session is
     started
    
    ---
     .../html/static/notebook/js/kernelselector.js   | 17 +++++++++++------
     .../html/static/services/sessions/js/session.js |  3 +++
     2 files changed, 14 insertions(+), 6 deletions(-)
    
    diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js
    index 8dcf7142a..c9f3f86d2 100644
    --- a/IPython/html/static/notebook/js/kernelselector.js
    +++ b/IPython/html/static/notebook/js/kernelselector.js
    @@ -12,12 +12,14 @@ define([
             this.selector = selector;
             this.notebook = notebook;
             this.kernelspecs = {};
    -        this.current = "python";
             if (this.selector !== undefined) {
                 this.element = $(selector);
                 this.style();
                 this.request_kernelspecs();
             }
    +
    +        // For now, this is how we make this object available elsewhere
    +        IPython.kernelselector = this;
         };
         
         KernelSelector.prototype.style = function() {
    @@ -43,16 +45,19 @@ define([
         };
     
         KernelSelector.prototype.change_kernel = function(kernel_name) {
    -        console.log("change_kernel " + kernel_name + " from " + this.current);
    -        if (kernel_name === this.current) {
    +        if (kernel_name === this.notebook.kernel.name) {
                 return;
             }
             this.notebook.session.delete();
             this.notebook.start_session(kernel_name);
    -        this.current = kernel_name;
    -        var display_name = this.kernelspecs[kernel_name].display_name;
    -        this.element.find("#current_kernel_spec").text(display_name);
         };
         
    +    KernelSelector.prototype.set_displayed_name = function(kernel_name) {
    +        var ks = this.kernelspecs[kernel_name]
    +        if (ks !== undefined) {
    +            this.element.find("#current_kernel_spec").text(ks.display_name);
    +        }
    +    };
    +
         return {'KernelSelector': KernelSelector};
     });
    diff --git a/IPython/html/static/services/sessions/js/session.js b/IPython/html/static/services/sessions/js/session.js
    index 34c699627..1271eaae7 100644
    --- a/IPython/html/static/services/sessions/js/session.js
    +++ b/IPython/html/static/services/sessions/js/session.js
    @@ -91,9 +91,12 @@ 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;
             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);
    +        IPython.kernelselector.set_displayed_name(this.kernel_name);
         };
         
         /**
    
    From 6f8c4b8dc4fbd8c98ed4880763584b4c8855309d Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Sat, 12 Jul 2014 16:05:52 -0500
    Subject: [PATCH 03/17] Style kernel name
    
    ---
     IPython/html/static/notebook/less/kernelselector.less | 6 ++++++
     IPython/html/static/notebook/less/style.less          | 1 +
     IPython/html/static/style/style.min.css               | 6 ++++++
     3 files changed, 13 insertions(+)
     create mode 100644 IPython/html/static/notebook/less/kernelselector.less
    
    diff --git a/IPython/html/static/notebook/less/kernelselector.less b/IPython/html/static/notebook/less/kernelselector.less
    new file mode 100644
    index 000000000..fd263eed6
    --- /dev/null
    +++ b/IPython/html/static/notebook/less/kernelselector.less
    @@ -0,0 +1,6 @@
    +span#kernel_selector_widget {
    +    padding: 0px 5px;
    +    margin-top: 12px;
    +    margin-right: 1em;
    +    font-size: 120%;
    +}
    diff --git a/IPython/html/static/notebook/less/style.less b/IPython/html/static/notebook/less/style.less
    index af3b0893f..3d26c90d4 100644
    --- a/IPython/html/static/notebook/less/style.less
    +++ b/IPython/html/static/notebook/less/style.less
    @@ -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";
    diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
    index 9ffd03f0b..2cf16d500 100644
    --- a/IPython/html/static/style/style.min.css
    +++ b/IPython/html/static/style/style.min.css
    @@ -9436,6 +9436,12 @@ p {
     .completions select option.context {
       color: #3071a9;
     }
    +span#kernel_selector_widget {
    +  padding: 0px 5px;
    +  margin-top: 12px;
    +  margin-right: 1em;
    +  font-size: 120%;
    +}
     #menubar {
       margin-top: 0px;
       margin-bottom: -19px;
    
    From 7400dd1c187c50e6c94082528317fdf60c83e23d Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Sat, 12 Jul 2014 16:15:18 -0500
    Subject: [PATCH 04/17] Line dropdown up on RHS of kernel name
    
    ---
     IPython/html/static/notebook/less/kernelselector.less | 1 +
     IPython/html/static/style/style.min.css               | 1 +
     2 files changed, 2 insertions(+)
    
    diff --git a/IPython/html/static/notebook/less/kernelselector.less b/IPython/html/static/notebook/less/kernelselector.less
    index fd263eed6..1b5821fb2 100644
    --- a/IPython/html/static/notebook/less/kernelselector.less
    +++ b/IPython/html/static/notebook/less/kernelselector.less
    @@ -3,4 +3,5 @@ span#kernel_selector_widget {
         margin-top: 12px;
         margin-right: 1em;
         font-size: 120%;
    +    position: relative;
     }
    diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
    index 2cf16d500..ced0d1144 100644
    --- a/IPython/html/static/style/style.min.css
    +++ b/IPython/html/static/style/style.min.css
    @@ -9441,6 +9441,7 @@ span#kernel_selector_widget {
       margin-top: 12px;
       margin-right: 1em;
       font-size: 120%;
    +  position: relative;
     }
     #menubar {
       margin-top: 0px;
    
    From b644ddbe36bfd4376c6eb780894f0916f1e11adc Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Sat, 12 Jul 2014 16:51:39 -0500
    Subject: [PATCH 05/17] Store kernelspec in notebook metadata on change
    
    ---
     IPython/html/static/notebook/js/notebook.js         | 11 +++++++++++
     IPython/html/static/services/sessions/js/session.js |  1 +
     2 files changed, 12 insertions(+)
    
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index 0af6642a8..57e03edec 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -336,6 +336,17 @@ define([
                 notebook: this,
                 keyboard_manager: this.keyboard_manager});
         };
    +    
    +    Notebook.prototype.set_kernelspec_metadata = function(kernel_name) {
    +        var ks = IPython.kernelselector.kernelspecs[kernel_name]
    +        var tostore = {};
    +        for (field in ks) {
    +            if (field !== 'argv' && field !== 'env') {
    +                tostore[field] = ks[field]
    +            }
    +        }
    +        this.metadata.kernelspec = tostore;
    +    }
     
         // Cell indexing, retrieval, etc.
     
    diff --git a/IPython/html/static/services/sessions/js/session.js b/IPython/html/static/services/sessions/js/session.js
    index 1271eaae7..30174b450 100644
    --- a/IPython/html/static/services/sessions/js/session.js
    +++ b/IPython/html/static/services/sessions/js/session.js
    @@ -97,6 +97,7 @@ define([
             this.kernel = new kernel.Kernel(kernel_service_url, this.ws_url, this.notebook, this.kernel_name);
             this.kernel._kernel_started(data.kernel);
             IPython.kernelselector.set_displayed_name(this.kernel_name);
    +        this.notebook.set_kernelspec_metadata(this.kernel_name);
         };
         
         /**
    
    From 54c6cb62c08df80ee2890f11646cc7abf456eb5a Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Sat, 12 Jul 2014 17:28:55 -0500
    Subject: [PATCH 06/17] Start kernel according to loaded notebook metadata
    
    ---
     IPython/html/static/notebook/js/notebook.js | 11 ++++++++++-
     1 file changed, 10 insertions(+), 1 deletion(-)
    
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index 57e03edec..77ac77e15 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -2231,7 +2231,16 @@ 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;
    +            
    +            // If we don't already know about this kernel spec, store its info
    +            var kernelspecs = IPython.kernelselector.kernelspecs;
    +            if (kernelspec !== {} && !(kernel_name in kernelspecs)) {
    +                kernelspecs[kernel_name] = kernelspec;
    +            }
    +
    +            this.start_session(kernel_name);
             }
             // load our checkpoint list
             this.list_checkpoints();
    
    From ba69cd972e143368f298c82af4489348ecb4f199 Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Tue, 15 Jul 2014 15:50:19 -0700
    Subject: [PATCH 07/17] Add method to change codemirror mode of all code cells
    
    ---
     IPython/html/static/notebook/js/notebook.js | 30 +++++++++++++++++++++
     1 file changed, 30 insertions(+)
    
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index 77ac77e15..813c60f4e 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -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();
    @@ -1499,6 +1500,35 @@ 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;
    +        IPython.CodeCell.options_default.cm_config.mode = newmode;
    +        modename = newmode.name || newmode
    +
    +        CodeMirror.requireMode(modename, function(){
    +            cells = IPython.notebook.get_cells();
    +            for(var i in cells){
    +                c = cells[i];
    +                if (c.cell_type === 'code'){
    +                    c.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.
    +                    c.cm_config.mode = newmode;
    +                }
    +            }
    +        })
    +    };
     
         // Session related things
     
    
    From e51f26275aee667f2a1e3ddf8573b6019ea0b461 Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Tue, 15 Jul 2014 16:13:12 -0700
    Subject: [PATCH 08/17] Set codemirror mode from kernelspecs
    
    ---
     IPython/html/static/notebook/js/notebook.js | 12 ++++++++++++
     1 file changed, 12 insertions(+)
    
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index 813c60f4e..496ec4161 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -347,6 +347,11 @@ define([
                 }
             }
             this.metadata.kernelspec = tostore;
    +        
    +        // Update the codemirror mode for code cells
    +        if (tostore.codemirror_mode) {
    +            this.set_codemirror_mode(tostore.codemirror_mode);
    +        }
         }
     
         // Cell indexing, retrieval, etc.
    @@ -1775,6 +1780,13 @@ define([
             this.metadata = content.metadata;
             this.notebook_name = data.name;
             var trusted = true;
    +        
    +        // Set the default codemirror mode before we load the cells
    +        var cm_mode = (this.metadata.kernelspec || {}).codemirror_mode;
    +        if (cm_mode) {
    +            this.set_codemirror_mode(cm_mode);
    +        }
    +        
             // Only handle 1 worksheet for now.
             var worksheet = content.worksheets[0];
             if (worksheet !== undefined) {
    
    From 01d43290af12c9d5ade12e62ae05b64da009e16c Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Mon, 21 Jul 2014 14:08:28 -0700
    Subject: [PATCH 09/17] Remove unused style() function
    
    ---
     IPython/html/static/notebook/js/kernelselector.js | 4 ----
     1 file changed, 4 deletions(-)
    
    diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js
    index c9f3f86d2..ab85176c4 100644
    --- a/IPython/html/static/notebook/js/kernelselector.js
    +++ b/IPython/html/static/notebook/js/kernelselector.js
    @@ -14,7 +14,6 @@ define([
             this.kernelspecs = {};
             if (this.selector !== undefined) {
                 this.element = $(selector);
    -            this.style();
                 this.request_kernelspecs();
             }
     
    @@ -22,9 +21,6 @@ define([
             IPython.kernelselector = this;
         };
         
    -    KernelSelector.prototype.style = function() {
    -    };
    -    
         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)});
    
    From c432af1af5ab509d027de92aae75828425453e13 Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Mon, 21 Jul 2014 15:59:13 -0700
    Subject: [PATCH 10/17] Use JS events for switching kernelspecs
    
    ---
     .../html/static/notebook/js/kernelselector.js | 15 +++++++-----
     IPython/html/static/notebook/js/notebook.js   | 23 ++++++++++---------
     .../static/services/sessions/js/session.js    | 12 ++++++----
     3 files changed, 29 insertions(+), 21 deletions(-)
    
    diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js
    index ab85176c4..a2d8db9d4 100644
    --- a/IPython/html/static/notebook/js/kernelselector.js
    +++ b/IPython/html/static/notebook/js/kernelselector.js
    @@ -11,12 +11,13 @@ define([
         var KernelSelector = function(selector, notebook) {
             this.selector = selector;
             this.notebook = notebook;
    +        this.events = notebook.events;
             this.kernelspecs = {};
             if (this.selector !== undefined) {
                 this.element = $(selector);
                 this.request_kernelspecs();
             }
    -
    +        this.bind_events();
             // For now, this is how we make this object available elsewhere
             IPython.kernelselector = this;
         };
    @@ -44,15 +45,17 @@ define([
             if (kernel_name === this.notebook.kernel.name) {
                 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.set_displayed_name = function(kernel_name) {
    -        var ks = this.kernelspecs[kernel_name]
    -        if (ks !== undefined) {
    -            this.element.find("#current_kernel_spec").text(ks.display_name);
    -        }
    +    KernelSelector.prototype.bind_events = function() {
    +        var that = this;
    +        this.events.on('spec_changed.Kernel', function(event, data) {
    +            that.element.find("#current_kernel_spec").text(data.display_name);
    +        });
         };
     
         return {'KernelSelector': KernelSelector};
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index 496ec4161..7879d3ee0 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -215,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
    @@ -338,8 +345,7 @@ define([
                 keyboard_manager: this.keyboard_manager});
         };
         
    -    Notebook.prototype.set_kernelspec_metadata = function(kernel_name) {
    -        var ks = IPython.kernelselector.kernelspecs[kernel_name]
    +    Notebook.prototype.set_kernelspec_metadata = function(ks) {
             var tostore = {};
             for (field in ks) {
                 if (field !== 'argv' && field !== 'env') {
    @@ -347,11 +353,6 @@ define([
                 }
             }
             this.metadata.kernelspec = tostore;
    -        
    -        // Update the codemirror mode for code cells
    -        if (tostore.codemirror_mode) {
    -            this.set_codemirror_mode(tostore.codemirror_mode);
    -        }
         }
     
         // Cell indexing, retrieval, etc.
    @@ -1781,10 +1782,10 @@ define([
             this.notebook_name = data.name;
             var trusted = true;
             
    -        // Set the default codemirror mode before we load the cells
    -        var cm_mode = (this.metadata.kernelspec || {}).codemirror_mode;
    -        if (cm_mode) {
    -            this.set_codemirror_mode(cm_mode);
    +        // 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.
    diff --git a/IPython/html/static/services/sessions/js/session.js b/IPython/html/static/services/sessions/js/session.js
    index 30174b450..f84623d1d 100644
    --- a/IPython/html/static/services/sessions/js/session.js
    +++ b/IPython/html/static/services/sessions/js/session.js
    @@ -91,13 +91,17 @@ 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;
    +        // If we asked for 'python', the response will have 'python3' or 'python2'.
    +        // In this case, fire the spec changed event again to update the name
    +        // and highlighting.
    +        if (data.kernel.name !== this.kernel_name) {
    +            this.kernel_name = data.kernel.name;
    +            var ks = IPython.kernelselector.kernelspecs[this.kernel_name];
    +            this.notebook.events.trigger('spec_changed.Kernel', ks);
    +        }
             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);
    -        IPython.kernelselector.set_displayed_name(this.kernel_name);
    -        this.notebook.set_kernelspec_metadata(this.kernel_name);
         };
         
         /**
    
    From 0e3d27c841e7e2f5e0d9492e4ca1845af9082ef3 Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Mon, 21 Jul 2014 16:16:31 -0700
    Subject: [PATCH 11/17] Deterministic order for kernels in notebook UI
    
    ---
     IPython/html/services/kernelspecs/handlers.py | 4 +++-
     1 file changed, 3 insertions(+), 1 deletion(-)
    
    diff --git a/IPython/html/services/kernelspecs/handlers.py b/IPython/html/services/kernelspecs/handlers.py
    index dbe8382a8..6561b0bc0 100644
    --- a/IPython/html/services/kernelspecs/handlers.py
    +++ b/IPython/html/services/kernelspecs/handlers.py
    @@ -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)
    
    From 849ff3bb053d968a018c63569873f92080056a4a Mon Sep 17 00:00:00 2001
    From: Matthias BUSSONNIER 
    Date: Tue, 22 Jul 2014 16:51:12 +0200
    Subject: [PATCH 12/17] Simplify and uniformise styling of kernel selector
    
    Make the theme coherent with the logout button
    ---
     .../html/static/notebook/js/kernelselector.js |   2 +-
     .../static/notebook/less/kernelselector.less  |  17 ++-
     IPython/html/static/style/style.min.css       | 119 +++++++++++++++++-
     IPython/html/templates/notebook.html          |   8 +-
     4 files changed, 133 insertions(+), 13 deletions(-)
    
    diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js
    index a2d8db9d4..efb9032d0 100644
    --- a/IPython/html/static/notebook/js/kernelselector.js
    +++ b/IPython/html/static/notebook/js/kernelselector.js
    @@ -54,7 +54,7 @@ define([
         KernelSelector.prototype.bind_events = function() {
             var that = this;
             this.events.on('spec_changed.Kernel', function(event, data) {
    -            that.element.find("#current_kernel_spec").text(data.display_name);
    +            that.element.find("#current_kernel_spec").find('.kernel_name').text(data.display_name);
             });
         };
     
    diff --git a/IPython/html/static/notebook/less/kernelselector.less b/IPython/html/static/notebook/less/kernelselector.less
    index 1b5821fb2..575797806 100644
    --- a/IPython/html/static/notebook/less/kernelselector.less
    +++ b/IPython/html/static/notebook/less/kernelselector.less
    @@ -1,7 +1,14 @@
    -span#kernel_selector_widget {
    -    padding: 0px 5px;
    -    margin-top: 12px;
    +#kernel_selector_widget {
         margin-right: 1em;
    -    font-size: 120%;
    -    position: relative;
    +    float:right;
    +
    +    & > button {
    +        .btn();
    +        .btn-default();
    +        .btn-sm();
    +        
    +        & > span.caret {
    +            margin-top:0px;
    +        }
    +    }
     }
    diff --git a/IPython/html/static/style/style.min.css b/IPython/html/static/style/style.min.css
    index ced0d1144..aa381db40 100644
    --- a/IPython/html/static/style/style.min.css
    +++ b/IPython/html/static/style/style.min.css
    @@ -9436,12 +9436,121 @@ p {
     .completions select option.context {
       color: #3071a9;
     }
    -span#kernel_selector_widget {
    -  padding: 0px 5px;
    -  margin-top: 12px;
    +#kernel_selector_widget {
       margin-right: 1em;
    -  font-size: 120%;
    -  position: relative;
    +  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;
    diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html
    index 0ad6185af..1f759d7a0 100644
    --- a/IPython/html/templates/notebook.html
    +++ b/IPython/html/templates/notebook.html
    @@ -34,14 +34,18 @@ class="notebook_app"
     
     {% block header %}
     
    +
     
         
         
         
     
     
    -
    -    Python
    +
    +    
         
     
    
    From 2785e87b4ba90522f6909fad41065206aba6cd06 Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Thu, 24 Jul 2014 15:07:59 -0700
    Subject: [PATCH 13/17] Fix JS iteration
    
    Well, work around it.
    ---
     IPython/html/static/notebook/js/notebook.js | 21 ++++++++++-----------
     1 file changed, 10 insertions(+), 11 deletions(-)
    
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index 7879d3ee0..1e02b2c1c 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -347,11 +347,11 @@ define([
         
         Notebook.prototype.set_kernelspec_metadata = function(ks) {
             var tostore = {};
    -        for (field in ks) {
    +        $.map(ks, function(value, field) {
                 if (field !== 'argv' && field !== 'env') {
    -                tostore[field] = ks[field]
    +                tostore[field] = value;
                 }
    -        }
    +        });
             this.metadata.kernelspec = tostore;
         }
     
    @@ -1518,21 +1518,20 @@ define([
                 return;
             }
             this.codemirror_mode = newmode;
    -        IPython.CodeCell.options_default.cm_config.mode = newmode;
    +        codecell.CodeCell.options_default.cm_config.mode = newmode;
             modename = newmode.name || newmode
     
    +        that = this;
             CodeMirror.requireMode(modename, function(){
    -            cells = IPython.notebook.get_cells();
    -            for(var i in cells){
    -                c = cells[i];
    -                if (c.cell_type === 'code'){
    -                    c.code_mirror.setOption('mode', newmode);
    +            $.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.
    -                    c.cm_config.mode = newmode;
    +                    cell.cm_config.mode = newmode;
                     }
    -            }
    +            });
             })
         };
     
    
    From 76477386ae544d3e1fc3ecce4095d6b28a4476fd Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Thu, 24 Jul 2014 15:08:47 -0700
    Subject: [PATCH 14/17] Don't refer to global kernelselector object in Session
    
    ---
     IPython/html/static/notebook/js/kernelselector.js  | 14 +++++++++++++-
     .../html/static/services/sessions/js/session.js    | 10 +++-------
     2 files changed, 16 insertions(+), 8 deletions(-)
    
    diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js
    index efb9032d0..c78affad6 100644
    --- a/IPython/html/static/notebook/js/kernelselector.js
    +++ b/IPython/html/static/notebook/js/kernelselector.js
    @@ -12,6 +12,7 @@ define([
             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);
    @@ -42,7 +43,7 @@ define([
         };
     
         KernelSelector.prototype.change_kernel = function(kernel_name) {
    -        if (kernel_name === this.notebook.kernel.name) {
    +        if (kernel_name === this.current_selection) {
                 return;
             }
             var ks = this.kernelspecs[kernel_name];
    @@ -54,8 +55,19 @@ define([
         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};
    diff --git a/IPython/html/static/services/sessions/js/session.js b/IPython/html/static/services/sessions/js/session.js
    index f84623d1d..07339e70a 100644
    --- a/IPython/html/static/services/sessions/js/session.js
    +++ b/IPython/html/static/services/sessions/js/session.js
    @@ -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;
    @@ -92,13 +93,8 @@ 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'.
    -        // In this case, fire the spec changed event again to update the name
    -        // and highlighting.
    -        if (data.kernel.name !== this.kernel_name) {
    -            this.kernel_name = data.kernel.name;
    -            var ks = IPython.kernelselector.kernelspecs[this.kernel_name];
    -            this.notebook.events.trigger('spec_changed.Kernel', ks);
    -        }
    +        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);
    
    From 72e2c2c6d7c7f8b8577db8b73d36854e29c7ba47 Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Thu, 24 Jul 2014 16:25:32 -0700
    Subject: [PATCH 15/17] Remove last use of global kernelselector
    
    ---
     IPython/html/static/notebook/js/kernelselector.js | 2 --
     IPython/html/static/notebook/js/notebook.js       | 6 ------
     2 files changed, 8 deletions(-)
    
    diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js
    index c78affad6..908c25ee2 100644
    --- a/IPython/html/static/notebook/js/kernelselector.js
    +++ b/IPython/html/static/notebook/js/kernelselector.js
    @@ -19,8 +19,6 @@ define([
                 this.request_kernelspecs();
             }
             this.bind_events();
    -        // For now, this is how we make this object available elsewhere
    -        IPython.kernelselector = this;
         };
         
         KernelSelector.prototype.request_kernelspecs = function() {
    diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
    index 1e02b2c1c..7fefae789 100644
    --- a/IPython/html/static/notebook/js/notebook.js
    +++ b/IPython/html/static/notebook/js/notebook.js
    @@ -2275,12 +2275,6 @@ define([
             if (this.session === null) {
                 var kernelspec = this.metadata.kernelspec || {};
                 var kernel_name = kernelspec.name || this.default_kernel_name;
    -            
    -            // If we don't already know about this kernel spec, store its info
    -            var kernelspecs = IPython.kernelselector.kernelspecs;
    -            if (kernelspec !== {} && !(kernel_name in kernelspecs)) {
    -                kernelspecs[kernel_name] = kernelspec;
    -            }
     
                 this.start_session(kernel_name);
             }
    
    From c71eea365e895fd9727414ff739f5306c3e24334 Mon Sep 17 00:00:00 2001
    From: Thomas Kluyver 
    Date: Thu, 24 Jul 2014 18:14:11 -0700
    Subject: [PATCH 16/17] Add submenu under kernel for changing kernel
    
    ---
     IPython/html/static/notebook/js/kernelselector.js | 8 ++++++++
     IPython/html/templates/notebook.html              | 5 +++++
     2 files changed, 13 insertions(+)
    
    diff --git a/IPython/html/static/notebook/js/kernelselector.js b/IPython/html/static/notebook/js/kernelselector.js
    index 908c25ee2..35402d42d 100644
    --- a/IPython/html/static/notebook/js/kernelselector.js
    +++ b/IPython/html/static/notebook/js/kernelselector.js
    @@ -29,6 +29,8 @@ define([
         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");
    +        console.log(change_kernel_submenu);
             for (var i = 0; i < data.length; i++) {
                 var ks = data[i];
                 this.kernelspecs[ks.name] = ks;
    @@ -37,6 +39,12 @@ define([
                     .click($.proxy(this.change_kernel, this, ks.name))
                     .text(ks.display_name));
                 menu.append(ksentry);
    +
    +            var ks_submenu_entry = $("
  • ").attr("id", "kernel-submenu-"+ks.name).append($('') + .attr('href', '#') + .click($.proxy(this.change_kernel, this, ks.name)) + .text(ks.display_name)); + change_kernel_submenu.append(ks_submenu_entry); } }; diff --git a/IPython/html/templates/notebook.html b/IPython/html/templates/notebook.html index 1f759d7a0..68bb46c2a 100644 --- a/IPython/html/templates/notebook.html +++ b/IPython/html/templates/notebook.html @@ -226,6 +226,11 @@ class="notebook_app"
  • Restart
  • +
  • +