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