diff --git a/.travis.yml b/.travis.yml
index e64a7ab16..1a7c62fec 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,10 +26,12 @@ before_install:
- nvm install 5.6
- nvm use 5.6
- npm upgrade -g npm
+ - npm install
- 'if [[ $GROUP == js* ]]; then npm install -g casperjs@1.1.0-beta5; fi'
- git clone --quiet --depth 1 https://github.com/minrk/travis-wheels travis-wheels
install:
+ - npm run build
- pip install -f travis-wheels/wheelhouse file://$PWD#egg=notebook[test]
script:
diff --git a/notebook/static/base/js/keyboard.js b/notebook/static/base/js/keyboard.js
index 32c8dceca..5c3f3d2b0 100644
--- a/notebook/static/base/js/keyboard.js
+++ b/notebook/static/base/js/keyboard.js
@@ -168,20 +168,27 @@ define([
// Shortcut manager class
- var ShortcutManager = function (delay, events, actions, env) {
+ var ShortcutManager = function (delay, events, actions, env, config, mode) {
/**
* A class to deal with keyboard event and shortcut
*
* @class ShortcutManager
* @constructor
+ *
+ * :config: configobjet on which to call `update(....)` to persist the config.
+ * :mode: mode of this shortcut manager where to persist config.
*/
+ mode = mode || 'command';
this._shortcuts = {};
+ this._defaults_bindings = [];
this.delay = delay || 800; // delay in milliseconds
this.events = events;
this.actions = actions;
this.actions.extend_env(env);
this._queue = [];
this._cleartimeout = null;
+ this._config = config;
+ this._mode = mode;
Object.seal(this);
};
@@ -334,8 +341,30 @@ define([
}
};
+ ShortcutManager.prototype.is_available_shortcut = function(shortcut){
+ var shortcut_array = shortcut.split(',');
+ return this._is_available_shortcut(shortcut_array, this._shortcuts);
+ };
+
+ ShortcutManager.prototype._is_available_shortcut = function(shortcut_array, tree){
+ var current_node = tree[shortcut_array[0]];
+ if(!shortcut_array[0]){
+ return false;
+ }
+ if(current_node === undefined){
+ return true;
+ } else {
+ if (typeof(current_node) == 'string'){
+ return false;
+ } else { // assume is a sub-shortcut tree
+ return this._is_available_shortcut(shortcut_array.slice(1), current_node);
+ }
+ }
+ };
+
ShortcutManager.prototype._set_leaf = function(shortcut_array, action_name, tree){
var current_node = tree[shortcut_array[0]];
+
if(shortcut_array.length === 1){
if(current_node !== undefined && typeof(current_node) !== 'string'){
console.warn('[warning], you are overriting a long shortcut with a shorter one');
@@ -356,6 +385,50 @@ define([
}
};
+ ShortcutManager.prototype._persist_shortcut = function(shortcut, data) {
+ /**
+ * add a shortcut to this manager and persist it to the config file.
+ **/
+ shortcut = shortcut.toLowerCase();
+ this.add_shortcut(shortcut, data);
+ var patch = {keys:{}};
+ var b = {bind:{}};
+ patch.keys[this._mode] = {bind:{}};
+ patch.keys[this._mode].bind[shortcut] = data;
+ this._config.update(patch);
+ };
+
+ ShortcutManager.prototype._persist_remove_shortcut = function(shortcut){
+ /**
+ * Remove a shortcut from this manager and persist its removal.
+ */
+
+ shortcut = shortcut.toLowerCase();
+ this.remove_shortcut(shortcut);
+ var patch = {keys:{}};
+ var b = {bind:{}};
+ patch.keys[this._mode] = {bind:{}};
+ patch.keys[this._mode].bind[shortcut] = null;
+ this._config.update(patch);
+
+ // if the shortcut we unbind is a default one, we add it to the list of
+ // things to unbind at startup
+
+ if(this._defaults_bindings.indexOf(shortcut) !== -1){
+ var cnf = (this._config.data.keys||{})[this._mode];
+ var unbind_array = cnf.unbind||[];
+
+ // unless it's already there (like if we have remapped a default
+ // shortcut to another command, and unbind it)
+ if(unbind_array.indexOf(shortcut) !== -1){
+ unbind_array.concat(shortcut);
+ var unbind_patch = {keys:{unbind:unbind_array}};
+ this._config._update(unbind_patch);
+ }
+ }
+ };
+
+
ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
/**
* Add a action to be handled by shortcut manager.
@@ -369,8 +442,8 @@ define([
if (! action_name){
throw new Error('does not know how to deal with : ' + data);
}
- shortcut = normalize_shortcut(shortcut);
- this.set_shortcut(shortcut, action_name);
+ var _shortcut = normalize_shortcut(shortcut);
+ this.set_shortcut(_shortcut, action_name);
if (!suppress_help_update) {
// update the keyboard shortcuts notebook help
@@ -391,6 +464,16 @@ define([
this.events.trigger('rebuild.QuickHelp');
};
+ ShortcutManager.prototype._add_default_shortcuts = function (data) {
+ /**
+ * same as add_shortcuts, but register them as "default" that if persistently unbound, with
+ * persist_remove_shortcut, need to be on the "unbind" list.
+ **/
+ this._defaults_bindings = this._defaults_bindings.concat(Object.keys(data));
+ this.add_shortcuts(data);
+
+ };
+
ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
/**
* Remove the binding of shortcut `sortcut` with its action.
@@ -415,7 +498,7 @@ define([
this.events.trigger('rebuild.QuickHelp');
}
} catch (ex) {
- throw new Error('trying to remove a non-existent shortcut', shortcut);
+ throw new Error('trying to remove a non-existent shortcut', shortcut, typeof shortcut);
}
};
diff --git a/notebook/static/notebook/js/keyboardmanager.js b/notebook/static/notebook/js/keyboardmanager.js
index 4edb29935..411b53fad 100644
--- a/notebook/static/notebook/js/keyboardmanager.js
+++ b/notebook/static/notebook/js/keyboardmanager.js
@@ -36,12 +36,12 @@ define([
this.bind_events();
this.env = {pager:this.pager};
this.actions = options.actions;
- this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env );
- this.command_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
- this.command_shortcuts.add_shortcuts(this.get_default_command_shortcuts());
+ this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env, options.config, 'command');
+ this.command_shortcuts._add_default_shortcuts(this.get_default_common_shortcuts());
+ this.command_shortcuts._add_default_shortcuts(this.get_default_command_shortcuts());
this.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events, this.actions, this.env);
- this.edit_shortcuts.add_shortcuts(this.get_default_common_shortcuts());
- this.edit_shortcuts.add_shortcuts(this.get_default_edit_shortcuts());
+ this.edit_shortcuts._add_default_shortcuts(this.get_default_common_shortcuts());
+ this.edit_shortcuts._add_default_shortcuts(this.get_default_edit_shortcuts());
this.config = options.config;
@@ -106,7 +106,7 @@ define([
'ctrl-enter' : 'jupyter-notebook:run-cell',
'alt-enter' : 'jupyter-notebook:run-cell-and-insert-below',
// cmd on mac, ctrl otherwise
- 'cmdtrl-s' : 'jupyter-notebook:save-notebook',
+ 'cmdtrl-s' : 'jupyter-notebook:save-notebook'
};
};
diff --git a/notebook/static/notebook/js/quickhelp.js b/notebook/static/notebook/js/quickhelp.js
index f816bb31a..d2edbb2cc 100644
--- a/notebook/static/notebook/js/quickhelp.js
+++ b/notebook/static/notebook/js/quickhelp.js
@@ -153,13 +153,22 @@ define([
return hum;
}
- function humanize_shortcut(shortcut){
+ function _humanize_sequence(sequence){
+ var joinchar = ',';
+ var hum = _.map(sequence.replace(/meta/g, 'cmd').split(','), _humanize_shortcut).join(joinchar);
+ return hum;
+ }
+
+ function _humanize_shortcut(shortcut){
var joinchar = '-';
if (platform === 'MacOS'){
joinchar = '';
}
- var sh = _.map(shortcut.split('-'), humanize_key ).join(joinchar);
- return ''+sh+'';
+ return _.map(shortcut.split('-'), humanize_key ).join(joinchar);
+ }
+
+ function humanize_shortcut(shortcut){
+ return ''+_humanize_shortcut(shortcut)+'';
}
@@ -301,6 +310,7 @@ define([
return {'QuickHelp': QuickHelp,
humanize_shortcut: humanize_shortcut,
- humanize_sequence: humanize_sequence
+ humanize_sequence: humanize_sequence,
+ _humanize_sequence: _humanize_sequence,
};
});
diff --git a/notebook/tests/base/keyboard.js b/notebook/tests/base/keyboard.js
index f29a3587c..9a1a15400 100644
--- a/notebook/tests/base/keyboard.js
+++ b/notebook/tests/base/keyboard.js
@@ -71,21 +71,25 @@ casper.notebook_test(function () {
that.msgs = [];
that.on('remote.message', function(msg) {
that.msgs.push(msg);
- })
+ });
that.evaluate(function (obj) {
for(var k in obj){
- IPython.keyboard_manager.command_shortcuts.add_shortcut(k, function(){console.log(obj[k])});
+ if ({}.hasOwnProperty.call(obj, k)) {
+ IPython.keyboard_manager.command_shortcuts.add_shortcut(k, function(){console.log(obj[k]);});
+ }
}
}, shortcuts_test);
var longer_first = false;
var longer_last = false;
for(var m in that.msgs){
- longer_first = longer_first||(that.msgs[m].match(/you are overriting/)!= null);
- longer_last = longer_last ||(that.msgs[m].match(/will be shadowed/) != null);
+ if ({}.hasOwnProperty.call(that.msgs, m)) {
+ longer_first = longer_first||(that.msgs[m].match(/you are overriting/)!= null);
+ longer_last = longer_last ||(that.msgs[m].match(/will be shadowed/) != null);
+ }
}
this.test.assert(longer_first, 'no warning if registering shorter shortut');
this.test.assert(longer_last , 'no warning if registering longer shortut');
- })
+ });
});
diff --git a/package.json b/package.json
index 2ba8b0f5f..e8535c219 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,9 @@
"build:js:watch": "npm run build:js -- --watch"
},
"devDependencies": {
+ "babel-core": "^6.7.4",
+ "babel-loader": "^6.2.4",
+ "babel-preset-es2015": "^6.6.0",
"bower": "*",
"concurrently": "^1.0.0",
"less": "~2",
diff --git a/webpack.config.js b/webpack.config.js
index 1c6fa8e28..56e0f53a0 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -14,6 +14,7 @@ var commonConfig = {
},
module: {
loaders: [
+ { test: /\.js$/, exclude: /node_modules|\/notebook\/static\/component/, loader: "babel-loader"},
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ test: /\.json$/, loader: "json-loader" },
// jquery-ui loads some images