diff --git a/IPython/html/static/base/js/keyboard.js b/IPython/html/static/base/js/keyboard.js
index c8aa919..474b240 100644
--- a/IPython/html/static/base/js/keyboard.js
+++ b/IPython/html/static/base/js/keyboard.js
@@ -12,18 +12,22 @@ define([
'base/js/namespace',
'jquery',
'base/js/utils',
-], function(IPython, $, utils) {
+ 'underscore',
+], function(IPython, $, utils, _) {
"use strict";
- // Setup global keycodes and inverse keycodes.
+ /**
+ * Setup global keycodes and inverse keycodes.
+ *
+ * See http://unixpapa.com/js/key.html for a complete description. The short of
+ * it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
+ * and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
+ * but have minor differences.
+ **/
- // See http://unixpapa.com/js/key.html for a complete description. The short of
- // it is that there are different keycode sets. Firefox uses the "Mozilla keycodes"
- // and Webkit/IE use the "IE keycodes". These keycode sets are mostly the same
- // but have minor differences.
-
- // These apply to Firefox, (Webkit and IE)
+ // These apply to Firefox, (Webkit and IE)
+ // This does work **only** on US keyboard.
var _keycodes = {
'a': 65, 'b': 66, 'c': 67, 'd': 68, 'e': 69, 'f': 70, 'g': 71, 'h': 72, 'i': 73,
'j': 74, 'k': 75, 'l': 76, 'm': 77, 'n': 78, 'o': 79, 'p': 80, 'q': 81, 'r': 82,
@@ -84,13 +88,32 @@ define([
};
var normalize_shortcut = function (shortcut) {
- // Put a shortcut into normalized form:
- // 1. Make lowercase
- // 2. Replace cmd by meta
- // 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
- // 4. Normalize keys
+ /**
+ * @function _normalize_shortcut
+ * @private
+ * return a dict containing the normalized shortcut and the number of time it should be pressed:
+ *
+ * Put a shortcut into normalized form:
+ * 1. Make lowercase
+ * 2. Replace cmd by meta
+ * 3. Sort '-' separated modifiers into the order alt-ctrl-meta-shift
+ * 4. Normalize keys
+ **/
+ if (platform === 'MacOS') {
+ shortcut = shortcut.toLowerCase().replace('cmdtrl-', 'cmd-');
+ } else {
+ shortcut = shortcut.toLowerCase().replace('cmdtrl-', 'ctrl-');
+ }
+
shortcut = shortcut.toLowerCase().replace('cmd', 'meta');
shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
+ shortcut = shortcut.replace(/,$/, 'comma'); // catch shortcuts using '-' key
+ if(shortcut.indexOf(',') !== -1){
+ var sht = shortcut.split(',');
+ sht = _.map(sht, normalize_shortcut);
+ return shortcut;
+ }
+ shortcut = shortcut.replace(/comma/g, ','); // catch shortcuts using '-' key
var values = shortcut.split("-");
if (values.length === 1) {
return normalize_key(values[0]);
@@ -103,7 +126,9 @@ define([
};
var shortcut_to_event = function (shortcut, type) {
- // Convert a shortcut (shift-r) to a jQuery Event object
+ /**
+ * Convert a shortcut (shift-r) to a jQuery Event object
+ **/
type = type || 'keydown';
shortcut = normalize_shortcut(shortcut);
shortcut = shortcut.replace(/-$/, '_'); // catch shortcuts using '-' key
@@ -118,8 +143,21 @@ define([
return $.Event(type, opts);
};
+ var only_modifier_event = function(event){
+ /**
+ * Return `true` if the event only contains modifiers keys.
+ * false otherwise
+ **/
+ var key = inv_keycodes[event.which];
+ return ((event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) &&
+ (key === 'alt'|| key === 'ctrl'|| key === 'meta'|| key === 'shift'));
+
+ };
+
var event_to_shortcut = function (event) {
- // Convert a jQuery Event object to a shortcut (shift-r)
+ /**
+ * Convert a jQuery Event object to a normalized shortcut string (shift-r)
+ **/
var shortcut = '';
var key = inv_keycodes[event.which];
if (event.altKey && key !== 'alt') {shortcut += 'alt-';}
@@ -132,7 +170,7 @@ define([
// Shortcut manager class
- var ShortcutManager = function (delay, events) {
+ var ShortcutManager = function (delay, events, actions, env) {
/**
* A class to deal with keyboard event and shortcut
*
@@ -140,33 +178,78 @@ define([
* @constructor
*/
this._shortcuts = {};
- this._counts = {};
- this._timers = {};
this.delay = delay || 800; // delay in milliseconds
this.events = events;
+ this.actions = actions;
+ this.actions.extend_env(env);
+ this._queue = [];
+ this._cleartimeout = null;
+ Object.seal(this);
+ };
+
+ ShortcutManager.prototype.clearsoon = function(){
+ /**
+ * Clear the pending shortcut soon, and cancel previous clearing
+ * that might be registered.
+ **/
+ var that = this;
+ clearTimeout(this._cleartimeout);
+ this._cleartimeout = setTimeout(function(){that.clearqueue();}, this.delay);
+ };
+
+
+ ShortcutManager.prototype.clearqueue = function(){
+ /**
+ * clear the pending shortcut sequence now.
+ **/
+ this._queue = [];
+ clearTimeout(this._cleartimeout);
+ };
+
+
+ var flatten_shorttree = function(tree){
+ /**
+ * Flatten a tree of shortcut sequences.
+ * use full to iterate over all the key/values of available shortcuts.
+ **/
+ var dct = {};
+ for(var key in tree){
+ var value = tree[key];
+ if(typeof(value) === 'string'){
+ dct[key] = value;
+ } else {
+ var ftree=flatten_shorttree(value);
+ for(var subkey in ftree){
+ dct[key+','+subkey] = ftree[subkey];
+ }
+ }
+ }
+ return dct;
};
ShortcutManager.prototype.help = function () {
var help = [];
- for (var shortcut in this._shortcuts) {
- var help_string = this._shortcuts[shortcut].help;
- var help_index = this._shortcuts[shortcut].help_index;
+ var ftree = flatten_shorttree(this._shortcuts);
+ for (var shortcut in ftree) {
+ var action = this.actions.get(ftree[shortcut]);
+ var help_string = action.help||'== no help ==';
+ var help_index = action.help_index;
if (help_string) {
- if (platform === 'MacOS') {
- shortcut = shortcut.replace('meta', 'cmd');
- }
+ var shortstring = (action.shortstring||shortcut);
help.push({
- shortcut: shortcut,
+ shortcut: shortstring,
help: help_string,
help_index: help_index}
);
}
}
help.sort(function (a, b) {
- if (a.help_index > b.help_index)
+ if (a.help_index > b.help_index){
return 1;
- if (a.help_index < b.help_index)
+ }
+ if (a.help_index < b.help_index){
return -1;
+ }
return 0;
});
return help;
@@ -176,19 +259,105 @@ define([
this._shortcuts = {};
};
- ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
- if (typeof(data) === 'function') {
- data = {help: '', help_index: '', handler: data};
+ ShortcutManager.prototype.get_shortcut = function (shortcut){
+ /**
+ * return a node of the shortcut tree which an action name (string) if leaf,
+ * and an object with `object.subtree===true`
+ **/
+ if(typeof(shortcut) === 'string'){
+ shortcut = shortcut.split(',');
+ }
+
+ return this._get_leaf(shortcut, this._shortcuts);
+ };
+
+
+ ShortcutManager.prototype._get_leaf = function(shortcut_array, tree){
+ /**
+ * @private
+ * find a leaf/node in a subtree of the keyboard shortcut
+ *
+ **/
+ if(shortcut_array.length === 1){
+ return tree[shortcut_array[0]];
+ } else if( typeof(tree[shortcut_array[0]]) !== 'string'){
+ return this._get_leaf(shortcut_array.slice(1), tree[shortcut_array[0]]);
+ }
+ return null;
+ };
+
+ ShortcutManager.prototype.set_shortcut = function( shortcut, action_name){
+ if( typeof(action_name) !== 'string'){ throw('action is not a string', action_name);}
+ if( typeof(shortcut) === 'string'){
+ shortcut = shortcut.split(',');
+ }
+ return this._set_leaf(shortcut, action_name, this._shortcuts);
+ };
+
+ ShortcutManager.prototype._is_leaf = function(shortcut_array, tree){
+ if(shortcut_array.length === 1){
+ return(typeof(tree[shortcut_array[0]]) === 'string');
+ } else {
+ var subtree = tree[shortcut_array[0]];
+ return this._is_leaf(shortcut_array.slice(1), subtree );
+ }
+ };
+
+ ShortcutManager.prototype._remove_leaf = function(shortcut_array, tree, allow_node){
+ if(shortcut_array.length === 1){
+ var current_node = tree[shortcut_array[0]];
+ if(typeof(current_node) === 'string'){
+ delete tree[shortcut_array[0]];
+ } else {
+ throw('try to delete non-leaf');
+ }
+ } else {
+ this._remove_leaf(shortcut_array.slice(1), tree[shortcut_array[0]], allow_node);
+ if(_.keys(tree[shortcut_array[0]]).length === 0){
+ delete tree[shortcut_array[0]];
+ }
+ }
+ };
+
+ 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');
+ }
+ tree[shortcut_array[0]] = action_name;
+ return true;
+ } else {
+ if(typeof(current_node) === 'string'){
+ console.warn('you are trying to set a shortcut that will be shadowed'+
+ 'by a more specific one. Aborting for :', action_name, 'the follwing '+
+ 'will take precedence', current_node);
+ return false;
+ } else {
+ tree[shortcut_array[0]] = tree[shortcut_array[0]]||{};
+ }
+ this._set_leaf(shortcut_array.slice(1), action_name, tree[shortcut_array[0]]);
+ return true;
}
- data.help_index = data.help_index || '';
- data.help = data.help || '';
- data.count = data.count || 1;
- if (data.help_index === '') {
- data.help_index = 'zz';
+ };
+
+ ShortcutManager.prototype.add_shortcut = function (shortcut, data, suppress_help_update) {
+ /**
+ * Add a action to be handled by shortcut manager.
+ *
+ * - `shortcut` should be a `Shortcut Sequence` of the for `Ctrl-Alt-C,Meta-X`...
+ * - `data` could be an `action name`, an `action` or a `function`.
+ * if a `function` is passed it will be converted to an anonymous `action`.
+ *
+ **/
+ var action_name = this.actions.get_name(data);
+ if (! action_name){
+ throw('does nto know how to deal with ', data);
}
+
shortcut = normalize_shortcut(shortcut);
- this._counts[shortcut] = 0;
- this._shortcuts[shortcut] = data;
+ this.set_shortcut(shortcut, action_name);
+
if (!suppress_help_update) {
// update the keyboard shortcuts notebook help
this.events.trigger('rebuild.QuickHelp');
@@ -196,6 +365,11 @@ define([
};
ShortcutManager.prototype.add_shortcuts = function (data) {
+ /**
+ * Convenient methods to call `add_shortcut(key, value)` on several items
+ *
+ * data : Dict of the form {key:value, ...}
+ **/
for (var shortcut in data) {
this.add_shortcut(shortcut, data[shortcut], true);
}
@@ -204,44 +378,22 @@ define([
};
ShortcutManager.prototype.remove_shortcut = function (shortcut, suppress_help_update) {
+ /**
+ * Remove the binding of shortcut `sortcut` with its action.
+ * throw an error if trying to remove a non-exiting shortcut
+ **/
shortcut = normalize_shortcut(shortcut);
- delete this._counts[shortcut];
- delete this._shortcuts[shortcut];
+ if( typeof(shortcut) === 'string'){
+ shortcut = shortcut.split(',');
+ }
+ this._remove_leaf(shortcut, this._shortcuts);
if (!suppress_help_update) {
// update the keyboard shortcuts notebook help
this.events.trigger('rebuild.QuickHelp');
}
};
- ShortcutManager.prototype.count_handler = function (shortcut, event, data) {
- /**
- * Seem to allow to call an handler only after several key press.
- * like, I suppose `dd` that delete the current cell only after
- * `d` has been pressed twice..
- * @method count_handler
- * @return {Boolean} `true|false`, whether or not the event has been handled.
- * @param shortcut {shortcut}
- * @param event {event}
- * @param data {data}
- */
- var that = this;
- var c = this._counts;
- var t = this._timers;
- var timer = null;
- if (c[shortcut] === data.count-1) {
- c[shortcut] = 0;
- timer = t[shortcut];
- if (timer) {clearTimeout(timer); delete t[shortcut];}
- return data.handler(event);
- } else {
- c[shortcut] = c[shortcut] + 1;
- timer = setTimeout(function () {
- c[shortcut] = 0;
- }, that.delay);
- t[shortcut] = timer;
- }
- return false;
- };
+
ShortcutManager.prototype.call_handler = function (event) {
/**
@@ -249,26 +401,40 @@ define([
* @method call_handler
* @return {Boolean} `true|false`, `false` if no handler was found, otherwise the value return by the handler.
* @param event {event}
- */
+ *
+ * given an event, call the corresponding shortcut.
+ * return false is event wan handled, true otherwise
+ * in any case returning false stop event propagation
+ **/
+
+
+ this.clearsoon();
+ if(only_modifier_event(event)){
+ return true;
+ }
var shortcut = event_to_shortcut(event);
- var data = this._shortcuts[shortcut];
- if (data) {
- var handler = data.handler;
- if (handler) {
- if (data.count === 1) {
- return handler(event);
- } else if (data.count > 1) {
- return this.count_handler(shortcut, event, data);
- }
- }
+ this._queue.push(shortcut);
+ var action_name = this.get_shortcut(this._queue);
+
+ if (typeof(action_name) === 'undefined'|| action_name === null){
+ this.clearqueue();
+ return true;
+ }
+
+ if (this.actions.exists(action_name)) {
+ event.preventDefault();
+ this.clearqueue();
+ return this.actions.call(action_name, event);
}
- return true;
+
+ return false;
};
+
ShortcutManager.prototype.handles = function (event) {
var shortcut = event_to_shortcut(event);
- var data = this._shortcuts[shortcut];
- return !( data === undefined || data.handler === undefined );
+ var action_name = this.get_shortcut(this._queue.concat(shortcut));
+ return (typeof(action_name) !== 'undefined');
};
var keyboard = {
@@ -278,7 +444,7 @@ define([
normalize_key : normalize_key,
normalize_shortcut : normalize_shortcut,
shortcut_to_event : shortcut_to_event,
- event_to_shortcut : event_to_shortcut
+ event_to_shortcut : event_to_shortcut,
};
// For backwards compatibility.
diff --git a/IPython/html/static/notebook/js/actions.js b/IPython/html/static/notebook/js/actions.js
new file mode 100644
index 0000000..f91091d
--- /dev/null
+++ b/IPython/html/static/notebook/js/actions.js
@@ -0,0 +1,503 @@
+// Copyright (c) IPython Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+define(['require'
+], function(require) {
+ "use strict";
+
+ var ActionHandler = function (env) {
+ this.env = env || {};
+ Object.seal(this);
+ };
+
+ /**
+ * A bunch of predefined `Simple Actions` used by IPython.
+ * `Simple Actions` have the following keys:
+ * help (optional): a short string the describe the action.
+ * will be used in various context, like as menu name, tool tips on buttons,
+ * and short description in help menu.
+ * help_index (optional): a string used to sort action in help menu.
+ * icon (optional): a short string that represent the icon that have to be used with this
+ * action. this should mainly correspond to a Font_awesome class.
+ * handler : a function which is called when the action is activated. It will receive at first parameter
+ * a dictionary containing various handle to element of the notebook.
+ *
+ * action need to be registered with a **name** that can be use to refer to this action.
+ *
+ *
+ * if `help` is not provided it will be derived by replacing any dash by space
+ * in the **name** of the action. It is advised to provide a prefix to action name to
+ * avoid conflict the prefix should be all lowercase and end with a dot `.`
+ * in the absence of a prefix the behavior of the action is undefined.
+ *
+ * All action provided by IPython are prefixed with `ipython.`.
+ *
+ * One can register extra actions or replace an existing action with another one is possible
+ * but is considered undefined behavior.
+ *
+ **/
+ var _action = {
+ 'run-select-next': {
+ icon: 'fa-play',
+ help : 'run cell, select below',
+ help_index : 'ba',
+ handler : function (env) {
+ env.notebook.execute_cell_and_select_below();
+ }
+ },
+ 'execute-in-place':{
+ help : 'run cell',
+ help_index : 'bb',
+ handler : function (env) {
+ env.notebook.execute_cell();
+ }
+ },
+ 'execute-and-insert-after':{
+ help : 'run cell, insert below',
+ help_index : 'bc',
+ handler : function (env) {
+ env.notebook.execute_cell_and_insert_below();
+ }
+ },
+ 'go-to-command-mode': {
+ help : 'command mode',
+ help_index : 'aa',
+ handler : function (env) {
+ env.notebook.command_mode();
+ }
+ },
+ 'split-cell-at-cursor': {
+ help : 'split cell',
+ help_index : 'ea',
+ handler : function (env) {
+ env.notebook.split_cell();
+ }
+ },
+ 'enter-edit-mode' : {
+ help_index : 'aa',
+ handler : function (env) {
+ env.notebook.edit_mode();
+ }
+ },
+ 'select-previous-cell' : {
+ help_index : 'da',
+ handler : function (env) {
+ var index = env.notebook.get_selected_index();
+ if (index !== 0 && index !== null) {
+ env.notebook.select_prev();
+ env.notebook.focus_cell();
+ }
+ }
+ },
+ 'select-next-cell' : {
+ help_index : 'db',
+ handler : function (env) {
+ var index = env.notebook.get_selected_index();
+ if (index !== (env.notebook.ncells()-1) && index !== null) {
+ env.notebook.select_next();
+ env.notebook.focus_cell();
+ }
+ }
+ },
+ 'cut-selected-cell' : {
+ icon: 'fa-cut',
+ help_index : 'ee',
+ handler : function (env) {
+ env.notebook.cut_cell();
+ }
+ },
+ 'copy-selected-cell' : {
+ icon: 'fa-copy',
+ help_index : 'ef',
+ handler : function (env) {
+ env.notebook.copy_cell();
+ }
+ },
+ 'paste-cell-before' : {
+ help_index : 'eg',
+ handler : function (env) {
+ env.notebook.paste_cell_above();
+ }
+ },
+ 'paste-cell-after' : {
+ icon: 'fa-paste',
+ help_index : 'eh',
+ handler : function (env) {
+ env.notebook.paste_cell_below();
+ }
+ },
+ 'insert-cell-before' : {
+ help_index : 'ec',
+ handler : function (env) {
+ env.notebook.insert_cell_above();
+ env.notebook.select_prev();
+ env.notebook.focus_cell();
+ }
+ },
+ 'insert-cell-after' : {
+ icon : 'fa-plus',
+ help_index : 'ed',
+ handler : function (env) {
+ env.notebook.insert_cell_below();
+ env.notebook.select_next();
+ env.notebook.focus_cell();
+ }
+ },
+ 'change-selected-cell-to-code-cell' : {
+ help : 'to code',
+ help_index : 'ca',
+ handler : function (env) {
+ env.notebook.to_code();
+ }
+ },
+ 'change-selected-cell-to-markdown-cell' : {
+ help : 'to markdown',
+ help_index : 'cb',
+ handler : function (env) {
+ env.notebook.to_markdown();
+ }
+ },
+ 'change-selected-cell-to-raw-cell' : {
+ help : 'to raw',
+ help_index : 'cc',
+ handler : function (env) {
+ env.notebook.to_raw();
+ }
+ },
+ 'change-selected-cell-to-heading-1' : {
+ help : 'to heading 1',
+ help_index : 'cd',
+ handler : function (env) {
+ env.notebook.to_heading(undefined, 1);
+ }
+ },
+ 'change-selected-cell-to-heading-2' : {
+ help : 'to heading 2',
+ help_index : 'ce',
+ handler : function (env) {
+ env.notebook.to_heading(undefined, 2);
+ }
+ },
+ 'change-selected-cell-to-heading-3' : {
+ help : 'to heading 3',
+ help_index : 'cf',
+ handler : function (env) {
+ env.notebook.to_heading(undefined, 3);
+ }
+ },
+ 'change-selected-cell-to-heading-4' : {
+ help : 'to heading 4',
+ help_index : 'cg',
+ handler : function (env) {
+ env.notebook.to_heading(undefined, 4);
+ }
+ },
+ 'change-selected-cell-to-heading-5' : {
+ help : 'to heading 5',
+ help_index : 'ch',
+ handler : function (env) {
+ env.notebook.to_heading(undefined, 5);
+ }
+ },
+ 'change-selected-cell-to-heading-6' : {
+ help : 'to heading 6',
+ help_index : 'ci',
+ handler : function (env) {
+ env.notebook.to_heading(undefined, 6);
+ }
+ },
+ 'toggle-output-visibility-selected-cell' : {
+ help : 'toggle output',
+ help_index : 'gb',
+ handler : function (env) {
+ env.notebook.toggle_output();
+ }
+ },
+ 'toggle-output-scrolling-selected-cell' : {
+ help : 'toggle output scrolling',
+ help_index : 'gc',
+ handler : function (env) {
+ env.notebook.toggle_output_scroll();
+ }
+ },
+ 'move-selected-cell-down' : {
+ icon: 'fa-arrow-down',
+ help_index : 'eb',
+ handler : function (env) {
+ env.notebook.move_cell_down();
+ }
+ },
+ 'move-selected-cell-up' : {
+ icon: 'fa-arrow-up',
+ help_index : 'ea',
+ handler : function (env) {
+ env.notebook.move_cell_up();
+ }
+ },
+ 'toggle-line-number-selected-cell' : {
+ help : 'toggle line numbers',
+ help_index : 'ga',
+ handler : function (env) {
+ env.notebook.cell_toggle_line_numbers();
+ }
+ },
+ 'show-keyboard-shortcut-help-dialog' : {
+ help_index : 'ge',
+ handler : function (env) {
+ env.quick_help.show_keyboard_shortcuts();
+ }
+ },
+ 'delete-cell': {
+ help_index : 'ej',
+ handler : function (env) {
+ env.notebook.delete_cell();
+ }
+ },
+ 'interrupt-kernel':{
+ icon: 'fa-stop',
+ help_index : 'ha',
+ handler : function (env) {
+ env.notebook.kernel.interrupt();
+ }
+ },
+ 'restart-kernel':{
+ icon: 'fa-repeat',
+ help_index : 'hb',
+ handler : function (env) {
+ env.notebook.restart_kernel();
+ }
+ },
+ 'undo-last-cell-deletion' : {
+ help_index : 'ei',
+ handler : function (env) {
+ env.notebook.undelete_cell();
+ }
+ },
+ 'merge-selected-cell-with-cell-after' : {
+ help : 'merge cell below',
+ help_index : 'ek',
+ handler : function (env) {
+ env.notebook.merge_cell_below();
+ }
+ },
+ 'close-pager' : {
+ help_index : 'gd',
+ handler : function (env) {
+ env.pager.collapse();
+ }
+ }
+
+ };
+
+ /**
+ * A bunch of `Advance actions` for IPython.
+ * Cf `Simple Action` plus the following properties.
+ *
+ * handler: first argument of the handler is the event that triggerd the action
+ * (typically keypress). The handler is responsible for any modification of the
+ * event and event propagation.
+ * Is also responsible for returning false if the event have to be further ignored,
+ * true, to tell keyboard manager that it ignored the event.
+ *
+ * the second parameter of the handler is the environemnt passed to Simple Actions
+ *
+ **/
+ var custom_ignore = {
+ 'ignore':{
+ handler : function () {
+ return true;
+ }
+ },
+ 'move-cursor-up-or-previous-cell':{
+ handler : function (env, event) {
+ var index = env.notebook.get_selected_index();
+ var cell = env.notebook.get_cell(index);
+ var cm = env.notebook.get_selected_cell().code_mirror;
+ var cur = cm.getCursor();
+ if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
+ if(event){
+ event.preventDefault();
+ }
+ env.notebook.command_mode();
+ env.notebook.select_prev();
+ env.notebook.edit_mode();
+ cm = env.notebook.get_selected_cell().code_mirror;
+ cm.setCursor(cm.lastLine(), 0);
+ }
+ return false;
+ }
+ },
+ 'move-cursor-down-or-next-cell':{
+ handler : function (env, event) {
+ var index = env.notebook.get_selected_index();
+ var cell = env.notebook.get_cell(index);
+ if (cell.at_bottom() && index !== (env.notebook.ncells()-1)) {
+ if(event){
+ event.preventDefault();
+ }
+ env.notebook.command_mode();
+ env.notebook.select_next();
+ env.notebook.edit_mode();
+ var cm = env.notebook.get_selected_cell().code_mirror;
+ cm.setCursor(0, 0);
+ }
+ return false;
+ }
+ },
+ 'scroll-down': {
+ handler: function(env, event) {
+ if(event){
+ event.preventDefault();
+ }
+ return env.notebook.scroll_manager.scroll(1);
+ },
+ },
+ 'scroll-up': {
+ handler: function(env, event) {
+ if(event){
+ event.preventDefault();
+ }
+ return env.notebook.scroll_manager.scroll(-1);
+ },
+ },
+ 'save-notebook':{
+ help: "Save and Checkpoint",
+ help_index : 'fb',
+ icon: 'fa-save',
+ handler : function (env, event) {
+ env.notebook.save_checkpoint();
+ if(event){
+ event.preventDefault();
+ }
+ return false;
+ }
+ },
+ };
+
+ // private stuff that prepend `.ipython` to actions names
+ // and uniformize/fill in missing pieces in of an action.
+ var _prepare_handler = function(registry, subkey, source){
+ registry['ipython.'+subkey] = {};
+ registry['ipython.'+subkey].help = source[subkey].help||subkey.replace(/-/g,' ');
+ registry['ipython.'+subkey].help_index = source[subkey].help_index;
+ registry['ipython.'+subkey].icon = source[subkey].icon;
+ return source[subkey].handler;
+ };
+
+ // Will actually generate/register all the IPython actions
+ var fun = function(){
+ var final_actions = {};
+ for(var k in _action){
+ // Js closure are function level not block level need to wrap in a IIFE
+ // and append ipython to event name these things do intercept event so are wrapped
+ // in a function that return false.
+ var handler = _prepare_handler(final_actions, k, _action);
+ (function(key, handler){
+ final_actions['ipython.'+key].handler = function(env, event){
+ handler(env);
+ if(event){
+ event.preventDefault();
+ }
+ return false;
+ };
+ })(k, handler);
+ }
+
+ for(var k in custom_ignore){
+ // Js closure are function level not block level need to wrap in a IIFE
+ // same as above, but decide for themselves wether or not they intercept events.
+ var handler = _prepare_handler(final_actions, k, custom_ignore);
+ (function(key, handler){
+ final_actions['ipython.'+key].handler = function(env, event){
+ return handler(env, event);
+ };
+ })(k, handler);
+ }
+
+ return final_actions;
+ };
+ ActionHandler.prototype._actions = fun();
+
+
+ /**
+ * extend the environment variable that will be pass to handlers
+ **/
+ ActionHandler.prototype.extend_env = function(env){
+ for(var k in env){
+ this.env[k] = env[k];
+ }
+ };
+
+ ActionHandler.prototype.register = function(action, name, prefix){
+ /**
+ * Register an `action` with an optional name and prefix.
+ *
+ * if name and prefix are not given they will be determined automatically.
+ * if action if just a `function` it will be wrapped in an anonymous action.
+ *
+ * @return the full name to access this action .
+ **/
+ action = this.normalise(action);
+ if( !name ){
+ name = 'autogenerated-'+String(action.handler);
+ }
+ prefix = prefix || 'auto';
+ var full_name = prefix+'.'+name;
+ this._actions[full_name] = action;
+ return full_name;
+
+ };
+
+
+ ActionHandler.prototype.normalise = function(data){
+ /**
+ * given an `action` or `function`, return a normalised `action`
+ * by setting all known attributes and removing unknown attributes;
+ **/
+ if(typeof(data) === 'function'){
+ data = {handler:data};
+ }
+ if(typeof(data.handler) !== 'function'){
+ throw('unknown datatype, cannot register');
+ }
+ var _data = data;
+ data = {};
+ data.handler = _data.handler;
+ data.help = data.help || '';
+ data.icon = data.icon || '';
+ data.help_index = data.help_index || '';
+ return data;
+ };
+
+ ActionHandler.prototype.get_name = function(name_or_data){
+ /**
+ * given an `action` or `name` of a action, return the name attached to this action.
+ * if given the name of and corresponding actions does not exist in registry, return `null`.
+ **/
+
+ if(typeof(name_or_data) === 'string'){
+ if(this.exists(name_or_data)){
+ return name_or_data;
+ } else {
+ return null;
+ }
+ } else {
+ return this.register(name_or_data);
+ }
+ };
+
+ ActionHandler.prototype.get = function(name){
+ return this._actions[name];
+ };
+
+ ActionHandler.prototype.call = function(name, event, env){
+ return this._actions[name].handler(env|| this.env, event);
+ };
+
+ ActionHandler.prototype.exists = function(name){
+ return (typeof(this._actions[name]) !== 'undefined');
+ };
+
+ return {init:ActionHandler};
+
+});
diff --git a/IPython/html/static/notebook/js/keyboardmanager.js b/IPython/html/static/notebook/js/keyboardmanager.js
index 3127ddc..150d8ab 100644
--- a/IPython/html/static/notebook/js/keyboardmanager.js
+++ b/IPython/html/static/notebook/js/keyboardmanager.js
@@ -16,9 +16,6 @@ define([
], function(IPython, $, utils, keyboard) {
"use strict";
- var browser = utils.browser[0];
- var platform = utils.platform;
-
// Main keyboard manager for the notebook
var keycodes = keyboard.keycodes;
@@ -37,487 +34,119 @@ define([
this.pager = options.pager;
this.quick_help = undefined;
this.notebook = undefined;
+ this.last_mode = undefined;
this.bind_events();
- this.command_shortcuts = new keyboard.ShortcutManager(undefined, options.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.edit_shortcuts = new keyboard.ShortcutManager(undefined, options.events);
+ 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());
+ Object.seal(this);
};
+
+
+
/**
* Return a dict of common shortcut
* @method get_default_common_shortcuts
*
* @example Example of returned shortcut
* ```
- * 'shortcut-key': // a string representing the shortcut as dash separated value.
- * // e.g. 'shift' , 'shift-enter', 'cmd-t'
- * {
- * help: String // user facing help string
- * help_index: String // string used internally to order the shortcut on the quickhelp
- * handler: function(event){return true|false} // function that takes an even as first and only parameter
- * // and return a boolean indicating whether or not the event should been handled further.
- * }
+ * 'shortcut-key': 'action-name'
+ * // a string representing the shortcut as dash separated value.
+ * // e.g. 'shift' , 'shift-enter', 'cmd-t'
*```
*/
KeyboardManager.prototype.get_default_common_shortcuts = function() {
- var that = this;
- var shortcuts = {
- 'shift' : {
- help : '',
- help_index : '',
- handler : function (event) {
- // ignore shift keydown
- return true;
- }
- },
- 'shift-enter' : {
- help : 'run cell, select below',
- help_index : 'ba',
- handler : function (event) {
- that.notebook.execute_cell_and_select_below();
- return false;
- }
- },
- 'ctrl-enter' : {
- help : 'run cell',
- help_index : 'bb',
- handler : function (event) {
- that.notebook.execute_cell();
- return false;
- }
- },
- 'alt-enter' : {
- help : 'run cell, insert below',
- help_index : 'bc',
- handler : function (event) {
- that.notebook.execute_cell_and_insert_below();
- return false;
- }
- }
+ return {
+ 'shift' : 'ipython.ignore',
+ 'shift-enter' : 'ipython.run-select-next',
+ 'ctrl-enter' : 'ipython.execute-in-place',
+ 'alt-enter' : 'ipython.execute-and-insert-after',
+ // cmd on mac, ctrl otherwise
+ 'cmdtrl-s' : 'ipython.save-notebook',
};
-
- if (platform === 'MacOS') {
- shortcuts['cmd-s'] =
- {
- help : 'save notebook',
- help_index : 'fb',
- handler : function (event) {
- that.notebook.save_checkpoint();
- event.preventDefault();
- return false;
- }
- };
- } else {
- shortcuts['ctrl-s'] =
- {
- help : 'save notebook',
- help_index : 'fb',
- handler : function (event) {
- that.notebook.save_checkpoint();
- event.preventDefault();
- return false;
- }
- };
- }
- return shortcuts;
};
KeyboardManager.prototype.get_default_edit_shortcuts = function() {
- var that = this;
return {
- 'esc' : {
- help : 'command mode',
- help_index : 'aa',
- handler : function (event) {
- that.notebook.command_mode();
- return false;
- }
- },
- 'ctrl-m' : {
- help : 'command mode',
- help_index : 'ab',
- handler : function (event) {
- that.notebook.command_mode();
- return false;
- }
- },
- 'up' : {
- help : '',
- help_index : '',
- handler : function (event) {
- var index = that.notebook.get_selected_index();
- var cell = that.notebook.get_cell(index);
- var cm = that.notebook.get_selected_cell().code_mirror;
- var cur = cm.getCursor()
- if (cell && cell.at_top() && index !== 0 && cur.ch === 0) {
- event.preventDefault();
- that.notebook.command_mode();
- that.notebook.select_prev();
- that.notebook.edit_mode();
- var cm = that.notebook.get_selected_cell().code_mirror;
- cm.setCursor(cm.lastLine(), 0);
- }
- return false;
- }
- },
- 'down' : {
- help : '',
- help_index : '',
- handler : function (event) {
- var index = that.notebook.get_selected_index();
- var cell = that.notebook.get_cell(index);
- if (cell.at_bottom() && index !== (that.notebook.ncells()-1)) {
- event.preventDefault();
- that.notebook.command_mode();
- that.notebook.select_next();
- that.notebook.edit_mode();
- var cm = that.notebook.get_selected_cell().code_mirror;
- cm.setCursor(0, 0);
- return false;
- }
- return false;
- }
- },
- 'ctrl-shift--' : {
- help : 'split cell',
- help_index : 'ea',
- handler : function (event) {
- that.notebook.split_cell();
- return false;
- }
- },
- 'ctrl-shift-subtract' : {
- help : '',
- help_index : 'eb',
- handler : function (event) {
- that.notebook.split_cell();
- return false;
- }
- },
+ 'esc' : 'ipython.go-to-command-mode',
+ 'ctrl-m' : 'ipython.go-to-command-mode',
+ 'up' : 'ipython.move-cursor-up-or-previous-cell',
+ 'down' : 'ipython.move-cursor-down-or-next-cell',
+ 'ctrl-shift--' : 'ipython.split-cell-at-cursor',
+ 'ctrl-shift-subtract' : 'ipython.split-cell-at-cursor'
};
};
KeyboardManager.prototype.get_default_command_shortcuts = function() {
- var that = this;
return {
- 'space': {
- help: "Scroll down",
- handler: function(event) {
- return that.notebook.scroll_manager.scroll(1);
- },
- },
- 'shift-space': {
- help: "Scroll up",
- handler: function(event) {
- return that.notebook.scroll_manager.scroll(-1);
- },
- },
- 'enter' : {
- help : 'edit mode',
- help_index : 'aa',
- handler : function (event) {
- that.notebook.edit_mode();
- return false;
- }
- },
- 'up' : {
- help : 'select previous cell',
- help_index : 'da',
- handler : function (event) {
- var index = that.notebook.get_selected_index();
- if (index !== 0 && index !== null) {
- that.notebook.select_prev();
- that.notebook.focus_cell();
- }
- return false;
- }
- },
- 'down' : {
- help : 'select next cell',
- help_index : 'db',
- handler : function (event) {
- var index = that.notebook.get_selected_index();
- if (index !== (that.notebook.ncells()-1) && index !== null) {
- that.notebook.select_next();
- that.notebook.focus_cell();
- }
- return false;
- }
- },
- 'k' : {
- help : 'select previous cell',
- help_index : 'dc',
- handler : function (event) {
- var index = that.notebook.get_selected_index();
- if (index !== 0 && index !== null) {
- that.notebook.select_prev();
- that.notebook.focus_cell();
- }
- return false;
- }
- },
- 'j' : {
- help : 'select next cell',
- help_index : 'dd',
- handler : function (event) {
- var index = that.notebook.get_selected_index();
- if (index !== (that.notebook.ncells()-1) && index !== null) {
- that.notebook.select_next();
- that.notebook.focus_cell();
- }
- return false;
- }
- },
- 'x' : {
- help : 'cut cell',
- help_index : 'ee',
- handler : function (event) {
- that.notebook.cut_cell();
- return false;
- }
- },
- 'c' : {
- help : 'copy cell',
- help_index : 'ef',
- handler : function (event) {
- that.notebook.copy_cell();
- return false;
- }
- },
- 'shift-v' : {
- help : 'paste cell above',
- help_index : 'eg',
- handler : function (event) {
- that.notebook.paste_cell_above();
- return false;
- }
- },
- 'v' : {
- help : 'paste cell below',
- help_index : 'eh',
- handler : function (event) {
- that.notebook.paste_cell_below();
- return false;
- }
- },
- 'd' : {
- help : 'delete cell (press twice)',
- help_index : 'ej',
- count: 2,
- handler : function (event) {
- that.notebook.delete_cell();
- return false;
- }
- },
- 'a' : {
- help : 'insert cell above',
- help_index : 'ec',
- handler : function (event) {
- that.notebook.insert_cell_above();
- that.notebook.select_prev();
- that.notebook.focus_cell();
- return false;
- }
- },
- 'b' : {
- help : 'insert cell below',
- help_index : 'ed',
- handler : function (event) {
- that.notebook.insert_cell_below();
- that.notebook.select_next();
- that.notebook.focus_cell();
- return false;
- }
- },
- 'y' : {
- help : 'to code',
- help_index : 'ca',
- handler : function (event) {
- that.notebook.to_code();
- return false;
- }
- },
- 'm' : {
- help : 'to markdown',
- help_index : 'cb',
- handler : function (event) {
- that.notebook.to_markdown();
- return false;
- }
- },
- 'r' : {
- help : 'to raw',
- help_index : 'cc',
- handler : function (event) {
- that.notebook.to_raw();
- return false;
- }
- },
- '1' : {
- help : 'to heading 1',
- help_index : 'cd',
- handler : function (event) {
- that.notebook.to_heading(undefined, 1);
- return false;
- }
- },
- '2' : {
- help : 'to heading 2',
- help_index : 'ce',
- handler : function (event) {
- that.notebook.to_heading(undefined, 2);
- return false;
- }
- },
- '3' : {
- help : 'to heading 3',
- help_index : 'cf',
- handler : function (event) {
- that.notebook.to_heading(undefined, 3);
- return false;
- }
- },
- '4' : {
- help : 'to heading 4',
- help_index : 'cg',
- handler : function (event) {
- that.notebook.to_heading(undefined, 4);
- return false;
- }
- },
- '5' : {
- help : 'to heading 5',
- help_index : 'ch',
- handler : function (event) {
- that.notebook.to_heading(undefined, 5);
- return false;
- }
- },
- '6' : {
- help : 'to heading 6',
- help_index : 'ci',
- handler : function (event) {
- that.notebook.to_heading(undefined, 6);
- return false;
- }
- },
- 'o' : {
- help : 'toggle output',
- help_index : 'gb',
- handler : function (event) {
- that.notebook.toggle_output();
- return false;
- }
- },
- 'shift-o' : {
- help : 'toggle output scrolling',
- help_index : 'gc',
- handler : function (event) {
- that.notebook.toggle_output_scroll();
- return false;
- }
- },
- 's' : {
- help : 'save notebook',
- help_index : 'fa',
- handler : function (event) {
- that.notebook.save_checkpoint();
- return false;
- }
- },
- 'ctrl-j' : {
- help : 'move cell down',
- help_index : 'eb',
- handler : function (event) {
- that.notebook.move_cell_down();
- return false;
- }
- },
- 'ctrl-k' : {
- help : 'move cell up',
- help_index : 'ea',
- handler : function (event) {
- that.notebook.move_cell_up();
- return false;
- }
- },
- 'l' : {
- help : 'toggle line numbers',
- help_index : 'ga',
- handler : function (event) {
- that.notebook.cell_toggle_line_numbers();
- return false;
- }
- },
- 'i' : {
- help : 'interrupt kernel (press twice)',
- help_index : 'ha',
- count: 2,
- handler : function (event) {
- that.notebook.kernel.interrupt();
- return false;
- }
- },
- '0' : {
- help : 'restart kernel (press twice)',
- help_index : 'hb',
- count: 2,
- handler : function (event) {
- that.notebook.restart_kernel();
- return false;
- }
- },
- 'h' : {
- help : 'keyboard shortcuts',
- help_index : 'ge',
- handler : function (event) {
- that.quick_help.show_keyboard_shortcuts();
- return false;
- }
- },
- 'z' : {
- help : 'undo last delete',
- help_index : 'ei',
- handler : function (event) {
- that.notebook.undelete_cell();
- return false;
- }
- },
- 'shift-m' : {
- help : 'merge cell below',
- help_index : 'ek',
- handler : function (event) {
- that.notebook.merge_cell_below();
- return false;
- }
- },
- 'q' : {
- help : 'close pager',
- help_index : 'gd',
- handler : function (event) {
- that.pager.collapse();
- return false;
- }
- },
+ 'shift-space': 'ipython.scroll-up',
+ 'shift-v' : 'ipython.paste-cell-before',
+ 'shift-m' : 'ipython.merge-selected-cell-with-cell-after',
+ 'shift-o' : 'ipython.toggle-output-scrolling-selected-cell',
+ 'ctrl-j' : 'ipython.move-selected-cell-down',
+ 'ctrl-k' : 'ipython.move-selected-cell-up',
+ 'enter' : 'ipython.enter-edit-mode',
+ 'space' : 'ipython.scroll-down',
+ 'down' : 'ipython.select-next-cell',
+ 'i,i' : 'ipython.interrupt-kernel',
+ '0,0' : 'ipython.restart-kernel',
+ 'd,d' : 'ipython.delete-cell',
+ 'up' : 'ipython.select-previous-cell',
+ 'k' : 'ipython.select-previous-cell',
+ 'j' : 'ipython.select-next-cell',
+ 'x' : 'ipython.cut-selected-cell',
+ 'c' : 'ipython.copy-selected-cell',
+ 'v' : 'ipython.paste-cell-after',
+ 'a' : 'ipython.insert-cell-before',
+ 'b' : 'ipython.insert-cell-after',
+ 'y' : 'ipython.change-selected-cell-to-code-cell',
+ 'm' : 'ipython.change-selected-cell-to-markdown-cell',
+ 'r' : 'ipython.change-selected-cell-to-raw-cell',
+ '1' : 'ipython.change-selected-cell-to-heading-1',
+ '2' : 'ipython.change-selected-cell-to-heading-2',
+ '3' : 'ipython.change-selected-cell-to-heading-3',
+ '4' : 'ipython.change-selected-cell-to-heading-4',
+ '5' : 'ipython.change-selected-cell-to-heading-5',
+ '6' : 'ipython.change-selected-cell-to-heading-6',
+ 'o' : 'ipython.toggle-output-visibility-selected-cell',
+ 's' : 'ipython.save-notebook',
+ 'l' : 'ipython.toggle-line-number-selected-cell',
+ 'h' : 'ipython.show-keyboard-shortcut-help-dialog',
+ 'z' : 'ipython.undo-last-cell-deletion',
+ 'q' : 'ipython.close-pager',
};
};
KeyboardManager.prototype.bind_events = function () {
var that = this;
$(document).keydown(function (event) {
-
- if(event._ipkmIgnore==true||(event.originalEvent||{})._ipkmIgnore==true){
+ if(event._ipkmIgnore===true||(event.originalEvent||{})._ipkmIgnore===true){
return false;
}
return that.handle_keydown(event);
});
};
+ KeyboardManager.prototype.set_notebook = function (notebook) {
+ this.notebook = notebook;
+ this.actions.extend_env({notebook:notebook});
+ };
+
+ KeyboardManager.prototype.set_quickhelp = function (notebook) {
+ this.actions.extend_env({quick_help:notebook});
+ };
+
+
KeyboardManager.prototype.handle_keydown = function (event) {
- var notebook = this.notebook;
+ /**
+ * returning false from this will stop event propagation
+ **/
if (event.which === keycodes.esc) {
// Intercept escape at highest level to avoid closing
@@ -527,8 +156,7 @@ define([
if (!this.enabled) {
if (event.which === keycodes.esc) {
- // ESC
- notebook.command_mode();
+ this.notebook.command_mode();
return false;
}
return true;
@@ -595,7 +223,8 @@ define([
});
};
- // For backwards compatability.
+
+ // For backwards compatibility.
IPython.KeyboardManager = KeyboardManager;
return {'KeyboardManager': KeyboardManager};
diff --git a/IPython/html/static/notebook/js/main.js b/IPython/html/static/notebook/js/main.js
index 6c4a6a6..e7ff773 100644
--- a/IPython/html/static/notebook/js/main.js
+++ b/IPython/html/static/notebook/js/main.js
@@ -17,6 +17,7 @@ require([
'notebook/js/menubar',
'notebook/js/notificationarea',
'notebook/js/savewidget',
+ 'notebook/js/actions',
'notebook/js/keyboardmanager',
'notebook/js/config',
'notebook/js/kernelselector',
@@ -39,7 +40,8 @@ require([
quickhelp,
menubar,
notificationarea,
- savewidget,
+ savewidget,
+ actions,
keyboardmanager,
config,
kernelselector,
@@ -66,9 +68,11 @@ require([
var pager = new pager.Pager('div#pager', 'div#pager_splitter', {
layout_manager: layout_manager,
events: events});
+ var acts = new actions.init();
var keyboard_manager = new keyboardmanager.KeyboardManager({
pager: pager,
- events: events});
+ events: events,
+ actions: acts });
var save_widget = new savewidget.SaveWidget('span#save_widget', {
events: events,
keyboard_manager: keyboard_manager});
@@ -85,11 +89,14 @@ require([
var login_widget = new loginwidget.LoginWidget('span#login_widget', common_options);
var toolbar = new maintoolbar.MainToolBar('#maintoolbar-container', {
notebook: notebook,
- events: events});
+ events: events,
+ actions: acts});
var quick_help = new quickhelp.QuickHelp({
keyboard_manager: keyboard_manager,
events: events,
notebook: notebook});
+ keyboard_manager.set_notebook(notebook);
+ keyboard_manager.set_quickhelp(quick_help);
var menubar = new menubar.MenuBar('#menubar', $.extend({
notebook: notebook,
contents: contents,
diff --git a/IPython/html/static/notebook/js/quickhelp.js b/IPython/html/static/notebook/js/quickhelp.js
index 4bc179a..7792480 100644
--- a/IPython/html/static/notebook/js/quickhelp.js
+++ b/IPython/html/static/notebook/js/quickhelp.js
@@ -34,10 +34,10 @@ define([
platform_specific = [
{ shortcut: "Cmd-Up", help:"go to cell start" },
{ shortcut: "Cmd-Down", help:"go to cell end" },
- { shortcut: "Opt-Left", help:"go one word left" },
- { shortcut: "Opt-Right", help:"go one word right" },
- { shortcut: "Opt-Backspace", help:"del word before" },
- { shortcut: "Opt-Delete", help:"del word after" },
+ { shortcut: "Alt-Left", help:"go one word left" },
+ { shortcut: "Alt-Right", help:"go one word right" },
+ { shortcut: "Alt-Backspace", help:"del word before" },
+ { shortcut: "Alt-Delete", help:"del word after" },
];
} else {
// PC specific
@@ -65,10 +65,6 @@ define([
].concat( platform_specific );
-
-
-
-
QuickHelp.prototype.show_keyboard_shortcuts = function () {
// toggles display of keyboard shortcut dialog
var that = this;
@@ -139,7 +135,9 @@ define([
keys[i] = "" + k + "
";
continue; // leave individual keys lower-cased
}
- keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
+ if (k.indexOf(',') === -1){
+ keys[i] = ( special_case[k] ? special_case[k] : k.charAt(0).toUpperCase() + k.slice(1) );
+ }
keys[i] = "" + keys[i] + "
";
}
return keys.join('-');
diff --git a/IPython/html/static/notebook/js/toolbar.js b/IPython/html/static/notebook/js/toolbar.js
index 4040659..5f6c192 100644
--- a/IPython/html/static/notebook/js/toolbar.js
+++ b/IPython/html/static/notebook/js/toolbar.js
@@ -11,7 +11,7 @@ define([
* A generic toolbar on which one can add button
* @class ToolBar
* @constructor
- * @param {Dom_object} selector
+ * @param {Dom object} selector
*/
var ToolBar = function (selector, layout_manager) {
this.selector = selector;
diff --git a/IPython/html/tests/base/keyboard.js b/IPython/html/tests/base/keyboard.js
index d74113f..0c50cf0 100644
--- a/IPython/html/tests/base/keyboard.js
+++ b/IPython/html/tests/base/keyboard.js
@@ -13,7 +13,9 @@ var to_normalize = [
var unshifted = "` 1 2 3 4 5 6 7 8 9 0 - = q w e r t y u i o p [ ] \\ a s d f g h j k l ; ' z x c v b n m , . /";
// shifted = '~ ! @ # $ % ^ & * ( ) _ + Q W E R T Y U I O P { } | A S D F G H J K L : " Z X C V B N M < > ?';
+
casper.notebook_test(function () {
+ var that = this;
this.then(function () {
this.each(unshifted.split(' '), function (self, item) {
@@ -46,4 +48,37 @@ casper.notebook_test(function () {
});
});
+ this.then(function(){
+
+ var shortcuts_test = {
+ 'i,e,e,e,e,e' : '[[5E]]',
+ 'i,d,d,q,d' : '[[TEST1]]',
+ 'i,d,d' : '[[TEST1 WILL BE SHADOWED]]',
+ 'i,d,k' : '[[SHOULD SHADOW TEST2]]',
+ 'i,d,k,r,q' : '[[TEST2 NOT SHADOWED]]',
+ ';,up,down,up,down,left,right,left,right,b,a' : '[[KONAMI]]',
+ 'ctrl-x,meta-c,meta-b,u,t,t,e,r,f,l,y' : '[[XKCD]]'
+
+ };
+
+ 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])});
+ }
+ }, 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);
+ }
+ this.test.assert(longer_first, 'no warnign if registering shorter shortut');
+ this.test.assert(longer_last , 'no warnign if registering longer shortut');
+ })
+
});