Created
January 12, 2020 20:12
-
-
Save theY4Kman/93a1005c0a32470e5c96a56a6ec856b2 to your computer and use it in GitHub Desktop.
An IPython config file which adds some familiar key bindings, such as Ctrl+Home/End and Ctrl+Backspace. Supports prompt-toolkit v2 and v3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
This ipython config file installs a few extra prompt key bindings for a more | |
familiar editing interface. In particular, it adds support for: | |
- Ctrl+Home: | |
move to the beginning of the buffer | |
- Ctrl+End: | |
move to the end of the buffer | |
- Ctrl+Backspace: | |
delete the last word (only in supporting terminals, i.e. all modern terminals) | |
For weird, customized terminals like mine, a few additional bindings are supported: | |
- Ctrl+Enter (bound to F34): | |
Shift+Enter (bound to F35): | |
add a newline before the current line | |
- Ctrl+Shift+Enter (bound to F36): | |
execute the current buffer (alias of Esc, Enter) | |
""" | |
from enum import Enum | |
from itertools import chain | |
from prompt_toolkit import keys, application, VERSION as PTK_VERSION | |
from prompt_toolkit.input import ansi_escape_sequences, vt100_parser | |
from prompt_toolkit.key_binding import key_bindings, key_processor | |
from traitlets.config import Config | |
IS_PTK2 = IS_PTK3 = False | |
if ('3',) <= PTK_VERSION < ('4',): | |
IS_PTK3 = True | |
elif ('2',) <= PTK_VERSION < ('3',): | |
IS_PTK2 = True | |
def install_prompt_customizations(c: Config) -> None: | |
class StrEnum(str, Enum): | |
pass | |
class ExtraKeys(str, Enum): | |
value: str | |
ControlBackspace = 'c-backspace' | |
ControlEnter = 'c-enter' | |
ShiftEnter = 's-enter' | |
ControlShiftEnter = 'c-s-enter' | |
if IS_PTK3: | |
Keys = StrEnum('Keys', [(a.name, a.value) for a in chain(keys.Keys, ExtraKeys)], module='ipython_config') | |
Keys.__bases__ += (keys.Keys,) | |
keys.Keys = \ | |
key_bindings.Keys = \ | |
application.Keys = \ | |
vt100_parser.Keys = \ | |
Keys | |
def calculate_ALL_KEYS(): | |
return [k.value for k in Keys] | |
keys.ALL_KEYS = key_bindings.ALL_KEYS = calculate_ALL_KEYS() | |
elif IS_PTK2: | |
Keys = keys.Keys | |
Keys.ControlHome = 'c-home' | |
Keys.ControlEnd = 'c-end' | |
for key in ExtraKeys: | |
setattr(Keys, key.name, key.value) | |
def calculate_ALL_KEYS(): | |
return [getattr(Keys, k) for k in dir(Keys) if not k.startswith('_')] | |
keys.ALL_KEYS = key_bindings.ALL_KEYS = key_processor.ALL_KEYS = calculate_ALL_KEYS() | |
else: | |
# We only support v2 and v3 | |
return | |
ansi_escape_sequences.ANSI_SEQUENCES.update({ | |
'\x08': Keys.ControlBackspace, # Control-H (8) (Identical to '\b') | |
'\x1b[1;5H': Keys.ControlHome, | |
'\x1b[1;5F': Keys.ControlEnd, | |
'\x1b[21;5~': Keys.ControlEnter, # F34 | |
'\x1b[23;5~': Keys.ShiftEnter, # F35 | |
'\x1b[24;5~': Keys.ControlShiftEnter, # F36 | |
}) | |
ansi_escape_sequences.REVERSE_ANSI_SEQUENCES.clear() | |
ansi_escape_sequences.REVERSE_ANSI_SEQUENCES.update(ansi_escape_sequences._get_reverse_ansi_sequences()) | |
c.InteractiveShellApp.exec_lines = [SHELL_EXEC_LINES] | |
SHELL_EXEC_LINES = r''' | |
def __initialize_custom_keybindings(): | |
from prompt_toolkit import VERSION as PTK_VERSION | |
from prompt_toolkit.application.current import get_app | |
from prompt_toolkit.filters import ( | |
Condition, | |
emacs_insert_mode, | |
has_selection, | |
in_paste_mode, | |
is_multiline, | |
vi_insert_mode, | |
) | |
from prompt_toolkit.key_binding.bindings.named_commands import get_by_name, register | |
from prompt_toolkit.key_binding.key_processor import KeyPress, KeyPressEvent | |
from prompt_toolkit.keys import Keys | |
IS_PTK2 = IS_PTK3 = False | |
if ('3',) <= PTK_VERSION < ('4',): | |
IS_PTK3 = True | |
elif ('2',) <= PTK_VERSION < ('3',): | |
IS_PTK2 = True | |
E = KeyPressEvent | |
@register('insert-line-above') | |
def _(event: E) -> None: | |
""" | |
Newline, before current (in case of multiline input. | |
""" | |
event.current_buffer.insert_line_above(copy_margin=not in_paste_mode()) | |
@register('accept-buffer') | |
def _(event: E) -> None: | |
""" | |
Execute the current buffer | |
""" | |
event.current_buffer.validate_and_handle() | |
insert_mode = vi_insert_mode | emacs_insert_mode | |
@Condition | |
def has_text_before_cursor() -> bool: | |
return bool(get_app().current_buffer.text) | |
kb = get_ipython().pt_app.key_bindings | |
kb.add('c-backspace') \ | |
(get_by_name('backward-kill-word')) | |
kb.add('c-enter', filter=insert_mode & is_multiline) \ | |
(get_by_name('insert-line-above')) | |
kb.add('s-enter', filter=insert_mode & is_multiline) \ | |
(get_by_name('insert-line-above')) | |
kb.add('c-s-enter') \ | |
(get_by_name('accept-buffer')) | |
if IS_PTK2: | |
@register("beginning-of-buffer") | |
def beginning_of_buffer(event: E) -> None: | |
""" | |
Move to the start of the buffer. | |
""" | |
buff = event.current_buffer | |
buff.cursor_position = 0 | |
@register("end-of-buffer") | |
def end_of_buffer(event: E) -> None: | |
""" | |
Move to the end of the buffer. | |
""" | |
buff = event.current_buffer | |
buff.cursor_position = len(buff.text) | |
# Don't add c-home and c-end input sequences into the buffer | |
@kb.add('c-home') | |
@kb.add('c-end') | |
def _(event: E) -> None: | |
pass | |
kb.add('c-home')(get_by_name('beginning-of-buffer')) | |
kb.add('c-end')(get_by_name('end-of-buffer')) | |
__initialize_custom_keybindings() | |
del __initialize_custom_keybindings | |
None # hide output from ipython | |
''' | |
install_prompt_customizations(c) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment