├── .gitignore ├── LICENSE ├── README.md ├── install.py ├── jupyterpip.py ├── notebook_input_mode ├── README.md ├── main.js ├── notebook_input_mode.yaml └── styles.css └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # notebook_input_mode 2 | Jupyter nbextension supporting alternate input modes such as vim mode 3 | # Deprecated! 4 | 5 | Note that this extension has been deprecated in favor of [Select Codemirror Keymap](http://jupyter-contrib-nbextensions.readthedocs.io/en/latest/nbextensions/select_keymap/README.html), a `jupyter-contrib-nbextensions` sub-project. 6 | 7 | # Installation 8 | 9 | * `clone` and run `python setup.py develop` 10 | 11 | __or__ 12 | 13 | * `pip install 'git+https://github.com/asford/notebook_input_mode.git#egg=notebook_input_mode'` 14 | 15 | Within a notebook switch on vim mode via the menu bar `Edit -> Vim`. 16 | 17 | # Key Mappings 18 | tbd 19 | 20 | # Developer References 21 | * http://jupyter-notebook.readthedocs.org/en/latest/extending/frontend_extensions.html 22 | * http://jupyter-notebook.readthedocs.org/en/latest/frontend_config.html 23 | 24 | In JavaScript console use the following to get a list of notebook actions: 25 | ```javascript 26 | Object.keys(require('base/js/namespace').actions._actions); 27 | ``` 28 | -------------------------------------------------------------------------------- /install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from os import path 3 | 4 | from notebook.nbextensions import install_nbextension 5 | from notebook.services.config import ConfigManager 6 | 7 | install_nbextension( 8 | path.join(path.dirname(path.abspath(__file__)), 'notebook_input_mode'), user=True, verbose=2) 9 | 10 | cm = ConfigManager().update('notebook', {"load_extensions": {"notebook_input_mode/main": True}}) 11 | -------------------------------------------------------------------------------- /jupyterpip.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015, Jonathan Frederic 2 | # All rights reserved. 3 | 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | 7 | # * Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | 10 | # * Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | 14 | # * Neither the name of ipython-pip nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | def _is_root(): 30 | """Checks if the user is rooted.""" 31 | import ctypes, os 32 | try: 33 | return os.geteuid() == 0 34 | except AttributeError: 35 | return ctypes.windll.shell32.IsUserAnAdmin() != 0 36 | return False 37 | 38 | def cmdclass(path, enable=None, user=None): 39 | """Build nbextension cmdclass dict for the setuptools.setup method. 40 | 41 | Parameters 42 | ---------- 43 | path: str 44 | Directory relative to the setup file that the nbextension code lives in. 45 | enable: [str=None] 46 | Extension to "enable". Enabling an extension causes it to be loaded 47 | automatically by the IPython notebook. 48 | user: [bool=None] 49 | Whether or not the nbextension should be installed in user mode. 50 | If this is undefined, the script will install as user mode IF the 51 | installer is not sudo. 52 | 53 | Usage 54 | ----- 55 | For automatic loading: 56 | # Assuming `./extension` is the relative path to the JS files and 57 | # `./extension/main.js` is the file that you want automatically loaded. 58 | setup( 59 | name='extension', 60 | ... 61 | cmdclass=cmdclass('extension', 'extension/main'), 62 | ) 63 | 64 | For manual loading: 65 | # Assuming `./extension` is the relative path to the JS files. 66 | setup( 67 | name='extension', 68 | ... 69 | cmdclass=cmdclass('extension'), 70 | ) 71 | """ 72 | 73 | from setuptools.command.install import install 74 | from setuptools.command.develop import develop 75 | from os.path import dirname, abspath, join, exists, realpath 76 | from traceback import extract_stack 77 | 78 | # Check if the user flag was set. 79 | if user is None: 80 | user = not _is_root() 81 | 82 | # Get the path of the extension 83 | calling_file = extract_stack()[-2][0] 84 | fullpath = realpath(calling_file) 85 | if not exists(fullpath): 86 | raise Exception('Could not find path of setup file.') 87 | extension_dir = join(dirname(fullpath), path) 88 | 89 | # Installs the nbextension 90 | def run_nbextension_install(develop): 91 | try: 92 | # IPython/Jupyter 4.0 93 | from notebook.nbextensions import install_nbextension 94 | from notebook.services.config import ConfigManager 95 | except ImportError: 96 | # Pre-schism 97 | from IPython.html.nbextensions import install_nbextension 98 | from IPython.html.services.config import ConfigManager 99 | install_nbextension(extension_dir, symlink=develop, user=user, verbose=2) 100 | if enable is not None: 101 | print("Enabling the extension ...") 102 | cm = ConfigManager() 103 | cm.update('notebook', {"load_extensions": {enable: True}}) 104 | 105 | # Command used for standard installs 106 | class InstallCommand(install): 107 | def run(self): 108 | print("Installing Python module...") 109 | install.run(self) 110 | print("Installing nbextension ...") 111 | run_nbextension_install(False) 112 | 113 | # Command used for development installs (symlinks the JS) 114 | class DevelopCommand(develop): 115 | def run(self): 116 | print("Installing Python module...") 117 | develop.run(self) 118 | print("Installing nbextension ...") 119 | run_nbextension_install(True) 120 | 121 | return { 122 | 'install': InstallCommand, 123 | 'develop': DevelopCommand, 124 | } 125 | -------------------------------------------------------------------------------- /notebook_input_mode/README.md: -------------------------------------------------------------------------------- 1 | # notebook_input_mode 2 | Jupyter nbextension supporting alternate input modes such as vim mode 3 | 4 | Within a notebook switch on vim mode via the menu bar `Edit -> Vim`. 5 | 6 | # Key Mappings 7 | tbd 8 | 9 | # Developer References 10 | * http://jupyter-notebook.readthedocs.org/en/latest/extending/frontend_extensions.html 11 | * http://jupyter-notebook.readthedocs.org/en/latest/frontend_config.html 12 | 13 | In JavaScript console use the following to get a list of notebook actions: 14 | ```javascript 15 | Object.keys(require('base/js/namespace').actions._actions); 16 | ``` -------------------------------------------------------------------------------- /notebook_input_mode/main.js: -------------------------------------------------------------------------------- 1 | define([ 2 | 'base/js/namespace', 3 | 'jquery', 4 | 'base/js/utils', 5 | 'base/js/keyboard', 6 | 'services/config', 7 | 'notebook/js/cell', 8 | 'notebook/js/outputarea', 9 | 'notebook/js/completer', 10 | 'notebook/js/celltoolbar', 11 | 'codemirror/lib/codemirror', 12 | 'codemirror/mode/python/python', 13 | 'notebook/js/codemirror-ipython', 14 | 'codemirror/keymap/vim', 15 | 'codemirror/mode/meta', 16 | 'codemirror/addon/comment/comment', 17 | 'codemirror/addon/dialog/dialog', 18 | 'codemirror/addon/edit/closebrackets', 19 | 'codemirror/addon/edit/matchbrackets', 20 | 'codemirror/addon/search/searchcursor', 21 | 'codemirror/addon/search/search', 22 | ], function(Jupyter, 23 | $, 24 | utils, 25 | keyboard, 26 | configmod, 27 | cell, 28 | outputarea, 29 | completer, 30 | celltoolbar, 31 | CodeMirror, 32 | cmpython, 33 | cmip, 34 | cmvim 35 | ) { 36 | "use strict"; 37 | 38 | function flatten_shortcuts(shortcut_tree){ 39 | var result = {}; 40 | for (var p in shortcut_tree) { 41 | if (typeof(shortcut_tree[p]) == "string") { 42 | result[p] = shortcut_tree[p]; 43 | } else { 44 | var subresult = flatten_shortcuts(shortcut_tree[p]); 45 | for( var subp in subresult) { 46 | result[p + "," + subp] = subresult[subp]; 47 | } 48 | } 49 | } 50 | return result; 51 | } 52 | 53 | function update_shortcuts( shortcut_manager, updated_shortcuts ) { 54 | 55 | var current_shortcuts = _.invert( flatten_shortcuts( shortcut_manager._shortcuts )); 56 | 57 | for (var shortcut_action in _.invert(updated_shortcuts)) { 58 | if ( _.has( current_shortcuts, shortcut_action ) && shortcut_manager.get_shortcut( current_shortcuts[shortcut_action] ) ) { 59 | 60 | shortcut_manager.remove_shortcut( current_shortcuts[shortcut_action] ); 61 | } 62 | } 63 | 64 | shortcut_manager.add_shortcuts( updated_shortcuts ); 65 | } 66 | 67 | function set_default_mode() { 68 | console.log("Unable to reset to default mode, refresh notebook to set input mode."); 69 | } 70 | 71 | function set_vim_mode() { 72 | CodeMirror.commands.leaveCurrentMode = function(cm) { 73 | if ( cm.state.vim.insertMode ) { 74 | // Move from insert mode into command mode. 75 | CodeMirror.keyMap['vim-insert'].call('Esc', cm); 76 | } else if ( cm.state.vim.visualMode ) { 77 | // Move from visual mode to command mode. 78 | CodeMirror.keyMap['vim'].call('Esc', cm); 79 | } else { 80 | // Move to notebook command mode. 81 | Jupyter.notebook.command_mode(); 82 | Jupyter.notebook.focus_cell(); 83 | } 84 | }; 85 | 86 | var update_cm_config = function(cm_config) { 87 | cm_config["vimMode"] = true; 88 | cm_config["keyMap"] = 'vim'; 89 | cm_config["extraKeys"]["Esc"] = 'leaveCurrentMode'; 90 | cm_config["extraKeys"]["Ctrl-["] = 'leaveCurrentMode'; 91 | } 92 | 93 | var update_cm_to_default = function(code_mirror) { 94 | code_mirror.setOption("vimMode", cell.Cell.options_default.cm_config["vimMode"]); 95 | code_mirror.setOption("keyMap", cell.Cell.options_default.cm_config["keyMap"]); 96 | code_mirror.setOption("extraKeys", cell.Cell.options_default.cm_config["extraKeys"]); 97 | } 98 | 99 | update_cm_config( cell.Cell.options_default.cm_config ); 100 | 101 | Jupyter.notebook.get_cells().map( 102 | function(cell) { 103 | update_cm_to_default(cell.code_mirror); 104 | return cell; 105 | }); 106 | 107 | // Disable keyboard manager for code mirror dialogs, handles ':' triggered ex-mode dialog box in vim mode. 108 | // Manager is re-enabled by re-entry into notebook edit mode + cell normal mode after dialog closes 109 | function openDialog_keymap_wrapper(target, template, callback, options) { 110 | Jupyter.keyboard_manager.disable(); 111 | return target.call(this, template, callback, options); 112 | } 113 | CodeMirror.defineExtension("openDialog", _.wrap(CodeMirror.prototype.openDialog, openDialog_keymap_wrapper )); 114 | 115 | // Rebind shortcuts to more vim-like nature 116 | var edit = Jupyter.keyboard_manager.edit_shortcuts; 117 | var default_edit = Jupyter.keyboard_manager.get_default_edit_shortcuts(); 118 | edit.remove_shortcut("esc") 119 | edit.add_shortcut("shift-esc", default_edit["esc"]) 120 | 121 | var vim_command_shortcuts = { 122 | "ctrl-c" : "jupyter-notebook:interrupt-kernel", 123 | "ctrl-z" : "jupyter-notebook:restart-kernel", 124 | 125 | "d,d" : "jupyter-notebook:cut-cell", 126 | "y,y" : "jupyter-notebook:copy-cell", 127 | "u" : "jupyter-notebook:undo-cell-deletion", 128 | 129 | "p" : "jupyter-notebook:paste-cell-below", 130 | "shift-p" : "jupyter-notebook:paste-cell-above", 131 | 132 | "o" : "jupyter-notebook:insert-cell-below", 133 | "shift-o" : "jupyter-notebook:insert-cell-above", 134 | 135 | "i" : "jupyter-notebook:enter-edit-mode", 136 | "enter" : "jupyter-notebook:enter-edit-mode", 137 | 138 | "shift-j" : "jupyter-notebook:move-cell-down", 139 | "shift-k" : "jupyter-notebook:move-cell-up", 140 | 141 | "shift-/" : "jupyter-notebook:show-keyboard-shortcuts", 142 | "h" : "jupyter-notebook:toggle-cell-output-collapsed", 143 | "shift-h" : "jupyter-notebook:toggle-cell-output-scrolled", 144 | 145 | "`" : "jupyter-notebook:change-cell-to-code", 146 | "0" : "jupyter-notebook:change-cell-to-markdown", 147 | } 148 | 149 | update_shortcuts( Jupyter.keyboard_manager.command_shortcuts, vim_command_shortcuts ); 150 | 151 | }; 152 | 153 | function apply_input_mode(target_mode) { 154 | if (target_mode == 'vim') { 155 | set_vim_mode(); 156 | } else if( target_mode == 'default' ) { 157 | set_default_mode(); 158 | } else { 159 | console.log("Unknown input mode:", target_mode) 160 | } 161 | 162 | } 163 | 164 | function update_mode_menu( ) { 165 | var input_mode = Jupyter.notebook.config.data.notebook_input_mode || "default"; 166 | 167 | $("#edit_menu").find(".selected_input_mode").removeClass("selected_input_mode"); 168 | $("#edit_menu").find("#menu-keymap-" + input_mode).addClass("selected_input_mode"); 169 | } 170 | 171 | function update_input_mode(target_mode) { 172 | apply_input_mode( target_mode ); 173 | Jupyter.notebook.config.update({ notebook_input_mode : target_mode }).then( Jupyter.notebook.events.trigger("config_changed.notebook_input_mode")); 174 | }; 175 | 176 | return { 177 | // this will be called at extension loading time 178 | //--- 179 | load_ipython_extension: function(){ 180 | $('head').append(''); 181 | 182 | $("#edit_menu").append('
  • '); 183 | $("#edit_menu").append(''); 184 | $("#edit_menu").append(''); 185 | $("#edit_menu").append(''); 186 | 187 | $('#menu-keymap-vim').click(function () { update_input_mode('vim'); }); 188 | $('#menu-keymap-default').click(function () { update_input_mode('default'); }); 189 | Jupyter.notebook.events.on("config_changed.notebook_input_mode", update_mode_menu); 190 | 191 | var input_mode = Jupyter.notebook.config.data.notebook_input_mode || "default"; 192 | apply_input_mode(input_mode); 193 | Jupyter.notebook.events.trigger("config_changed.notebook_input_mode"); 194 | } 195 | }; 196 | }) 197 | -------------------------------------------------------------------------------- /notebook_input_mode/notebook_input_mode.yaml: -------------------------------------------------------------------------------- 1 | Type: IPython Notebook Extension 2 | Name: notebook_input_mode 3 | Description: Jupyter nbextension supporting alternate input modes such as vim mode 4 | Link: README.md 5 | Main: main.js 6 | Compatibility: Jupyter (4.x) -------------------------------------------------------------------------------- /notebook_input_mode/styles.css: -------------------------------------------------------------------------------- 1 | .selected_input_mode i.fa { 2 | padding: 0px 5px; 3 | } 4 | 5 | .selected_input_mode i.fa:before { 6 | content: "\f00c"; 7 | } 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from setuptools import setup 3 | from jupyterpip import cmdclass 4 | 5 | setup( 6 | name='notebook_input_mode', 7 | version="0.1b1", 8 | description="Jupyter notebook extension supporting optional vim-style keybindings.", 9 | author="Alex Ford", 10 | author_email="a.sewall.ford@gmail.com", 11 | license="Unlicense", 12 | packages=['notebook_input_mode'], 13 | install_requires=["notebook"], 14 | cmdclass=cmdclass('notebook_input_mode', 'notebook_input_mode/main'), 15 | ) 16 | --------------------------------------------------------------------------------