Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make keyboard shortcuts declarative #1234

Merged
merged 4 commits into from
Mar 24, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/source/extending/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ override the notebook's defaults with your own custom behavior.
contents
savehooks
handlers
keymaps
88 changes: 88 additions & 0 deletions docs/source/extending/keymaps.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
Customize keymaps
=================

.. note::

DEclarative Custom Keymaps is a provisional feature with unstable API which is not
guarantied to be keep in future versions odf the notebook, and can be
removed or changed without warnings.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spelling nits:

DEclarative -> Declarative
guarantied -> guaranteed
be keep -> be kept
odf -> of


The notebook shortcuts that are defined by jupyter both in edit mode an command
mode are configurable in the frontend configuration file
``~/.jupyter/nbconfig/notebook.json``. The modification of Keyboard shortcut
suffer of several limitations, mainly that your Browser and OS might prevent
certain shortcut to work correctly. If this is the case, there are
unfortunately not much than can be done. The second issue can arise with
keyboard that have a layout different than US English. Again even if we are
aware of the issue, there is not much we can do about that.

Shortcut are also limited by the underlying library that handle code and text
edition: CodeMirror. If some Keyboard shortcuts are conflicting, the method
describe below might not work to create new keyboard shortcuts, especially in
the ``edit`` mode of the notebook.


The 4 sections of interest in ``~/.jupyter/nbconfig/notebook.json`` are the following:

- ``keys.command.unbind``
- ``keys.edit.unbind``
- ``keys.command.bind``
- ``keys.edit.bind``

The first two section describe which default keyboard shortcut not to register
at notebook startup time. These are mostly useful if you need to ``unbind`` a
default keyboard shortcut before binding it to a new ``command``.

These two first sections apply respectively to the ``command`` and ``edit``
mode of the notebook. They take a list of shortcut to ``unbind``.

For example, to unbind the shortcut to split a cell at the position of the
cursor (``Ctrl-Shift-Minus``)use the following:

.. code:: javascript

// file ~/.jupyter/nbconfig/notebook.json

{
"keys": {
"edit": {
"unbind": [
"Ctrl-Shift-Minus"
]
},
},
}




The last two section describe which new keyboard shortcut to register
at notebook startup time, and which actions they trigger.

These two last sections apply respectively to the ``command`` and ``edit`` mode of the notebook.
They take a dictionary with shortcuts as ``keys`` and ``commands`` name as value.

For example, to bind the shortcut ``G,G,G`` (Press G three time in a row) in
command mode, to the command that restart the kernel and run all cells, use the
following:


.. code:: javascript

// file ~/.jupyter/nbconfig/notebook.json

{
"keys": {
"command": {
"bind": {
"G,G,G":"jupyter-notebook:restart-kernel-and-run-all-cells"
}
}
},
}




The name of the available ``commands`` can be find by hovering the right end of
a row in the command palette.
8 changes: 6 additions & 2 deletions notebook/static/base/js/keyboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,10 @@ define([
* Remove the binding of shortcut `sortcut` with its action.
* throw an error if trying to remove a non-exiting shortcut
**/
if(!shortcut){
console.warn('trying to remove empty shortcut');
return;
}
shortcut = normalize_shortcut(shortcut);
if( typeof(shortcut) === 'string'){
shortcut = shortcut.split(',');
Expand All @@ -404,8 +408,8 @@ define([
* The shortcut error should be explicit here, because it will be
* seen by users.
*/
try
{
var that = this;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something but it doesn't look like that is being used anywhere in this function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, just had an experiment here that I removed.

try {
this._remove_leaf(shortcut, this._shortcuts);
if (!suppress_help_update) {
// update the keyboard shortcuts notebook help
Expand Down
2 changes: 1 addition & 1 deletion notebook/static/deprecated-imports.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,4 @@
"notebookApp['" + modulePath + "']});`"].join(' '));
return notebookApp[modulePath];
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ define([
attachments_preset.push('attachments.edit');

CellToolbar.register_preset('Attachments', attachments_preset, notebook);
console.log('Attachments editing toolbar loaded.');

};
return {'register' : register}
return {'register' : register};
});
1 change: 0 additions & 1 deletion notebook/static/notebook/js/celltoolbarpresets/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ define([
example_preset.push('default.rawedit');

CellToolbar.register_preset('Edit Metadata', example_preset, notebook);
console.log('Default extension for cell metadata editing loaded.');
};
return {'register': register};
});
1 change: 0 additions & 1 deletion notebook/static/notebook/js/celltoolbarpresets/rawcell.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ define([
raw_cell_preset.push('raw_cell.select');

CellToolbar.register_preset('Raw Cell Format', raw_cell_preset, notebook);
console.log('Raw Cell Format toolbar preset loaded.');
};
return {'register': register};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ define([
slideshow_preset.push('slideshow.select');

CellToolbar.register_preset('Slideshow',slideshow_preset, notebook);
console.log('Slideshow extension for metadata editing loaded.');
};
return {'register': register};
});
2 changes: 1 addition & 1 deletion notebook/static/notebook/js/commandpalette.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ define(function(require){
group: ["group", "{{group}} command group"],
searchOnFocus: true,
mustSelectItem: true,
template: '<i class="fa fa-icon {{icon}}"></i>{{display}} <div class="pull-right {{mode_shortcut}}">{{shortcut}}</div>',
template: '<i class="fa fa-icon {{icon}}"></i>{{display}} <div title={{key}} class="pull-right {{mode_shortcut}}">{{shortcut}}</div>',
order: "asc",
source: src,
callback: {
Expand Down
40 changes: 40 additions & 0 deletions notebook/static/notebook/js/keyboardmanager.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,46 @@ define([
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.config = options.config;
var that = this;

this.config.loaded.then(function(){
var edit_unbind;

try {
edit_unbind = that.config.data.keys.edit.unbind;
} catch (e) {
if (e instanceof TypeError) {
edit_unbind = [];
} else {
throw e;
}
}

edit_unbind.forEach(function(u){that.edit_shortcuts.remove_shortcut(u);});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be shorted to:

edit_unbind.forEach(that.edit_shortcuts.remove_shortcut);

?

and for the command_unbind.forEach below.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I actually tried, and it was calling the prototype in the wrong context, telling me that undefined has no attribute i_dont-remember_what my guess is that forEach will actually .bind(..) the callback to something else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right. I think it's because a function has the scope of the object it was defined on, so by default remove_shortcut has the scope of the edit_shortcuts object.

ES6 gives a very concise way to get around most this problems with arrow functions, but alas, it's not supported by most browsers yet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I know. We could think of using es6 transpiler as now we use webpack.... we might be able to use:

edit_unbind.forEach(that.edit_shortcuts.remove_shortcut, that.edit_shortcuts); but I'm not sure if:

  1. it works,
  2. it's understandable by everyone.

So I'll be in favor of something obvious.


var command_unbind;

try {
command_unbind = that.config.data.keys.command.unbind;
} catch (e) {
if (e instanceof TypeError) {
command_unbind = [];
} else {
throw e;
}
}

command_unbind.forEach(function(u){that.command_shortcuts.remove_shortcut(u);});

that.command_shortcuts.add_shortcuts( ((that.config.data.keys||{}).command||{}).bind);
that.edit_shortcuts.add_shortcuts( ((that.config.data.keys||{}).edit ||{}).bind);

}
);

Object.seal(this);
};

Expand Down
4 changes: 3 additions & 1 deletion notebook/static/notebook/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ require([
var keyboard_manager = new keyboardmanager.KeyboardManager({
pager: pager,
events: events,
actions: acts });
actions: acts,
config: config_section,
});
var save_widget = new savewidget.SaveWidget('span#save_widget', {
events: events,
keyboard_manager: keyboard_manager});
Expand Down
3 changes: 2 additions & 1 deletion notebook/static/notebook/js/notebook.js
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ define(function (require) {
Object.seal(this);
};


Notebook.options_default = {
// can be any cell type, or the special values of
// 'above', 'below', or 'selected' to get the value from another cell.
Expand Down Expand Up @@ -2410,7 +2411,7 @@ define(function (require) {
var cell = cells[i];
cell.remove_unused_attachments();
}
}
};

/**
* Load a notebook from JSON (.ipynb).
Expand Down
2 changes: 1 addition & 1 deletion notebook/static/notebook/js/quickhelp.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ define([
doc.append(
'The Jupyter Notebook has two different keyboard input modes. <b>Edit mode</b> '+
'allows you to type code/text into a cell and is indicated by a green cell '+
'border. <b>Command mode</b> binds the keyboard to notebook level actions '+
'border. <b>Command mode</b> binds the keyboard to notebook level commands '+
'and is indicated by a grey cell border with a blue left margin.'
);
element.append(doc);
Expand Down
5 changes: 3 additions & 2 deletions notebook/static/notebook/less/commandpalette.less
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ ul.typeahead-list {
}

.no-shortcut{
display:none;
min-width: 20px;
color: transparent;
}

.command-shortcut:before{
content:"(command)";
content:"(command mode)";
padding-right:3px;
color:@gray-light;
}
Expand Down
4 changes: 2 additions & 2 deletions notebook/static/services/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ function(utils) {
if (this.classname) {
return this.section.data[this.classname] || {};
} else {
return this.section.data
return this.section.data;
}
};

Expand All @@ -92,7 +92,7 @@ function(utils) {
ConfigWithDefaults.prototype.get = function(key) {
var that = this;
return this.section.loaded.then(function() {
return that._class_data()[key] || that.defaults[key]
return that._class_data()[key] || that.defaults[key];
});
};

Expand Down