Skip to content

Instantly share code, notes, and snippets.

@theY4Kman
Created January 12, 2020 20:12
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save theY4Kman/93a1005c0a32470e5c96a56a6ec856b2 to your computer and use it in GitHub Desktop.
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 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