summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJonathan Slenders <jonathan@slenders.be>2016-12-17 15:26:45 +0100
committerJonathan Slenders <jonathan@slenders.be>2016-12-17 15:26:49 +0100
commit17047496500480fc2c7653e701da0fdd5e08bfd4 (patch)
tree3f26f0ff68dba4026c72e2be02ae728ef60abbff
parentb3e0f6c842cc1f92530fa700097d3599faa37877 (diff)
Refactoring of the key bindings.
- All the load functions now create a new `Registry` object. - Added `MergedRegistry` and `ConditionalRegistry`. - Added `prompt_toolkit.key_binding.defaults` for loading the default key bindings. - `prompt_toolkit.key_binding.manager` has been deprecated.
-rw-r--r--prompt_toolkit/application.py14
-rw-r--r--prompt_toolkit/key_binding/bindings/basic.py42
-rw-r--r--prompt_toolkit/key_binding/bindings/emacs.py50
-rw-r--r--prompt_toolkit/key_binding/bindings/utils.py25
-rw-r--r--prompt_toolkit/key_binding/bindings/vi.py47
-rw-r--r--prompt_toolkit/key_binding/defaults.py120
-rw-r--r--prompt_toolkit/key_binding/input_processor.py6
-rw-r--r--prompt_toolkit/key_binding/manager.py109
-rw-r--r--prompt_toolkit/key_binding/registry.py195
-rw-r--r--prompt_toolkit/shortcuts.py6
10 files changed, 432 insertions, 182 deletions
diff --git a/prompt_toolkit/application.py b/prompt_toolkit/application.py
index d54eb75b..7f13171b 100644
--- a/prompt_toolkit/application.py
+++ b/prompt_toolkit/application.py
@@ -8,7 +8,8 @@ from .filters import CLIFilter, to_cli_filter
from .key_binding.bindings.basic import load_basic_bindings
from .key_binding.bindings.emacs import load_emacs_bindings
from .key_binding.bindings.vi import load_vi_bindings
-from .key_binding.registry import Registry
+from .key_binding.registry import BaseRegistry
+from .key_binding.defaults import load_default_key_bindings
from .layout import Window
from .layout.containers import Container
from .layout.controls import BufferControl
@@ -49,8 +50,8 @@ class Application(object):
:param buffer: A :class:`~prompt_toolkit.buffer.Buffer` instance for the default buffer.
:param initial_focussed_buffer: Name of the buffer that is focussed during start-up.
:param key_bindings_registry:
- :class:`~prompt_toolkit.key_binding.registry.Registry` instance for the
- key bindings.
+ :class:`~prompt_toolkit.key_binding.registry.BaseRegistry` instance for
+ the key bindings.
:param clipboard: :class:`~prompt_toolkit.clipboard.base.Clipboard` to use.
:param on_abort: What to do when Control-C is pressed.
:param on_exit: What to do when Control-D is pressed.
@@ -102,7 +103,7 @@ class Application(object):
assert layout is None or isinstance(layout, Container)
assert buffer is None or isinstance(buffer, Buffer)
assert buffers is None or isinstance(buffers, (dict, BufferMapping))
- assert key_bindings_registry is None or isinstance(key_bindings_registry, Registry)
+ assert key_bindings_registry is None or isinstance(key_bindings_registry, BaseRegistry)
assert clipboard is None or isinstance(clipboard, Clipboard)
assert on_abort in AbortAction._all
assert on_exit in AbortAction._all
@@ -145,10 +146,7 @@ class Application(object):
self.style = style or DEFAULT_STYLE
if key_bindings_registry is None:
- key_bindings_registry = Registry()
- load_basic_bindings(key_bindings_registry)
- load_emacs_bindings(key_bindings_registry)
- load_vi_bindings(key_bindings_registry)
+ key_bindings_registry = load_default_key_bindings()
if get_title is None:
get_title = lambda: None
diff --git a/prompt_toolkit/key_binding/bindings/basic.py b/prompt_toolkit/key_binding/bindings/basic.py
index 03b99647..b1522e76 100644
--- a/prompt_toolkit/key_binding/bindings/basic.py
+++ b/prompt_toolkit/key_binding/bindings/basic.py
@@ -9,8 +9,8 @@ from prompt_toolkit.mouse_events import MouseEventType, MouseEvent
from prompt_toolkit.renderer import HeightIsUnknownError
from prompt_toolkit.utils import suspend_to_background_supported, is_windows
-from .utils import create_handle_decorator
from .named_commands import get_by_name
+from ..registry import Registry
__all__ = (
@@ -26,11 +26,10 @@ def if_no_repeat(event):
return not event.is_repeat
-def load_basic_bindings(registry, filter=Always()):
- assert isinstance(filter, CLIFilter)
-
+def load_basic_bindings():
+ registry = Registry()
insert_mode = ViInsertMode() | EmacsInsertMode()
- handle = create_handle_decorator(registry, filter)
+ handle = registry.add_binding
has_selection = HasSelection()
@handle(Keys.ControlA)
@@ -228,12 +227,16 @@ def load_basic_bindings(registry, filter=Always()):
event.current_buffer.insert_text(data)
+ return registry
+
-def load_mouse_bindings(registry, filter=Always()):
+def load_mouse_bindings():
"""
Key bindings, required for mouse support.
(Mouse events enter through the key binding system.)
"""
+ registry = Registry()
+
@registry.add_binding(Keys.Vt100MouseEvent)
def _(event):
"""
@@ -330,13 +333,15 @@ def load_mouse_bindings(registry, filter=Always()):
handler(event.cli, MouseEvent(position=Point(x=x, y=y),
event_type=event_type))
+ return registry
+
-def load_abort_and_exit_bindings(registry, filter=Always()):
+def load_abort_and_exit_bindings():
"""
Basic bindings for abort (Ctrl-C) and exit (Ctrl-D).
"""
- assert isinstance(filter, CLIFilter)
- handle = create_handle_decorator(registry, filter)
+ registry = Registry()
+ handle = registry.add_binding
@handle(Keys.ControlC)
def _(event):
@@ -352,31 +357,34 @@ def load_abort_and_exit_bindings(registry, filter=Always()):
handle(Keys.ControlD, filter=ctrl_d_condition)(get_by_name('end-of-file'))
+ return registry
-def load_basic_system_bindings(registry, filter=Always()):
+
+def load_basic_system_bindings():
"""
Basic system bindings (For both Emacs and Vi mode.)
"""
- assert isinstance(filter, CLIFilter)
- handle = create_handle_decorator(registry, filter)
+ registry = Registry()
suspend_supported = Condition(
lambda cli: suspend_to_background_supported())
- @handle(Keys.ControlZ, filter=suspend_supported)
+ @registry.add_binding(Keys.ControlZ, filter=suspend_supported)
def _(event):
"""
Suspend process to background.
"""
event.cli.suspend_to_background()
+ return registry
+
-def load_auto_suggestion_bindings(registry, filter=Always()):
+def load_auto_suggestion_bindings():
"""
Key bindings for accepting auto suggestion text.
"""
- assert isinstance(filter, CLIFilter)
- handle = create_handle_decorator(registry, filter)
+ registry = Registry()
+ handle = registry.add_binding
suggestion_available = Condition(
lambda cli:
@@ -393,3 +401,5 @@ def load_auto_suggestion_bindings(registry, filter=Always()):
if suggestion:
b.insert_text(suggestion.text)
+
+ return registry
diff --git a/prompt_toolkit/key_binding/bindings/emacs.py b/prompt_toolkit/key_binding/bindings/emacs.py
index eb08a813..3da47251 100644
--- a/prompt_toolkit/key_binding/bindings/emacs.py
+++ b/prompt_toolkit/key_binding/bindings/emacs.py
@@ -6,9 +6,9 @@ from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYST
from prompt_toolkit.filters import Always, Condition, EmacsMode, to_cli_filter, HasSelection, EmacsInsertMode, HasFocus, HasArg
from prompt_toolkit.completion import CompleteEvent
-from .utils import create_handle_decorator
from .scroll import scroll_page_up, scroll_page_down
from .named_commands import get_by_name
+from ..registry import Registry, ConditionalRegistry
__all__ = (
'load_emacs_bindings',
@@ -18,15 +18,15 @@ __all__ = (
)
-def load_emacs_bindings(registry, filter=Always()):
+def load_emacs_bindings():
"""
Some e-macs extensions.
"""
# Overview of Readline emacs commands:
# http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf
- filter = to_cli_filter(filter)
+ registry = ConditionalRegistry(Registry(), EmacsMode())
+ handle = registry.add_binding
- handle = create_handle_decorator(registry, filter & EmacsMode())
insert_mode = EmacsInsertMode()
has_selection = HasSelection()
@@ -298,24 +298,26 @@ def load_emacs_bindings(registry, filter=Always()):
unindent(buffer, from_, to + 1, count=event.arg)
+ return registry
-def load_emacs_open_in_editor_bindings(registry, filter=None):
+
+def load_emacs_open_in_editor_bindings():
"""
Pressing C-X C-E will open the buffer in an external editor.
"""
- handle = create_handle_decorator(registry, filter & EmacsMode())
- has_selection = HasSelection()
+ registry = Registry()
- @handle(Keys.ControlX, Keys.ControlE, filter= ~has_selection)
- def _(event):
- """
- Open editor.
- """
- event.current_buffer.open_in_editor(event.cli)
+ registry.add_binding(Keys.ControlX, Keys.ControlE,
+ filter=EmacsMode() & ~HasSelection())(
+ get_by_name('edit-and-execute-command'))
+ return registry
+
+
+def load_emacs_system_bindings():
+ registry = ConditionalRegistry(Registry(), EmacsMode())
+ handle = registry.add_binding
-def load_emacs_system_bindings(registry, filter=None):
- handle = create_handle_decorator(registry, filter & EmacsMode())
has_focus = HasFocus(SYSTEM_BUFFER)
@handle(Keys.Escape, '!', filter= ~has_focus)
@@ -347,11 +349,13 @@ def load_emacs_system_bindings(registry, filter=None):
# Focus previous buffer again.
event.cli.pop_focus()
+ return registry
+
-def load_emacs_search_bindings(registry, get_search_state=None, filter=None):
- filter = to_cli_filter(filter)
+def load_emacs_search_bindings(get_search_state=None):
+ registry = ConditionalRegistry(Registry(), EmacsMode())
+ handle = registry.add_binding
- handle = create_handle_decorator(registry, filter & EmacsMode())
has_focus = HasFocus(SEARCH_BUFFER)
assert get_search_state is None or callable(get_search_state)
@@ -430,16 +434,20 @@ def load_emacs_search_bindings(registry, get_search_state=None, filter=None):
def _(event):
incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg)
+ return registry
-def load_extra_emacs_page_navigation_bindings(registry, filter=None):
+
+def load_extra_emacs_page_navigation_bindings():
"""
Key bindings, for scrolling up and down through pages.
This are separate bindings, because GNU readline doesn't have them.
"""
- filter = to_cli_filter(filter)
- handle = create_handle_decorator(registry, filter & EmacsMode())
+ registry = ConditionalRegistry(Registry(), EmacsMode())
+ handle = registry.add_binding
handle(Keys.ControlV)(scroll_page_down)
handle(Keys.PageDown)(scroll_page_down)
handle(Keys.Escape, 'v')(scroll_page_up)
handle(Keys.PageUp)(scroll_page_up)
+
+ return registry
diff --git a/prompt_toolkit/key_binding/bindings/utils.py b/prompt_toolkit/key_binding/bindings/utils.py
deleted file mode 100644
index caf08c5c..00000000
--- a/prompt_toolkit/key_binding/bindings/utils.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from __future__ import unicode_literals
-from prompt_toolkit.filters import CLIFilter, Always
-
-__all__ = (
- 'create_handle_decorator',
-)
-
-def create_handle_decorator(registry, filter=Always()):
- """
- Create a key handle decorator, which is compatible with `Registry.handle`,
- but will chain the given filter to every key binding.
-
- :param filter: `CLIFilter`
- """
- assert isinstance(filter, CLIFilter)
-
- def handle(*keys, **kw):
- # Chain the given filter to the filter of this specific binding.
- if 'filter' in kw:
- kw['filter'] = kw['filter'] & filter
- else:
- kw['filter'] = filter
-
- return registry.add_binding(*keys, **kw)
- return handle
diff --git a/prompt_toolkit/key_binding/bindings/vi.py b/prompt_toolkit/key_binding/bindings/vi.py
index 4b20e9a1..2a3e1b69 100644
--- a/prompt_toolkit/key_binding/bindings/vi.py
+++ b/prompt_toolkit/key_binding/bindings/vi.py
@@ -11,9 +11,9 @@ from prompt_toolkit.keys import Keys
from prompt_toolkit.layout.utils import find_window_for_buffer_name
from prompt_toolkit.selection import SelectionType, SelectionState, PasteMode
-from .utils import create_handle_decorator
from .scroll import scroll_forward, scroll_backward, scroll_half_page_up, scroll_half_page_down, scroll_one_line_up, scroll_one_line_down, scroll_page_up, scroll_page_down
from .named_commands import get_by_name
+from ..registry import Registry, ConditionalRegistry
import prompt_toolkit.filters as filters
from six.moves import range
@@ -137,8 +137,7 @@ class TextObject(object):
return new_document, clipboard_data
-def load_vi_bindings(registry, enable_visual_key=Always(),
- get_search_state=None, filter=Always()):
+def load_vi_bindings(enable_visual_key=Always(), get_search_state=None):
"""
Vi extensions.
@@ -160,14 +159,15 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
# handled correctly. There is no need to add "~IsReadOnly" to all key
# bindings that do text manipulation.
+ registry = ConditionalRegistry(Registry(), ViMode())
+ handle = registry.add_binding
+
enable_visual_key = to_cli_filter(enable_visual_key)
# Default get_search_state.
if get_search_state is None:
def get_search_state(cli): return cli.search_state
- handle = create_handle_decorator(registry, filter & ViMode())
-
# (Note: Always take the navigation bindings in read-only mode, even when
# ViState says different.)
navigation_mode = ViNavigationMode()
@@ -1684,23 +1684,27 @@ def load_vi_bindings(registry, enable_visual_key=Always(),
event.cli.vi_state.waiting_for_digraph = False
event.cli.vi_state.digraph_symbol1 = None
+ return registry
+
-def load_vi_open_in_editor_bindings(registry, filter=None):
+def load_vi_open_in_editor_bindings():
"""
Pressing 'v' in navigation mode will open the buffer in an external editor.
"""
- navigation_mode = ViNavigationMode()
- handle = create_handle_decorator(registry, filter & ViMode())
+ registry = Registry()
+ navigation_mode = ViMode() & ViNavigationMode()
+
+ registry.add_binding('v')(get_by_name('edit-and-execute-command'))
+ return registry
- handle('v', filter=navigation_mode)(get_by_name('edit-and-execute-command'))
+def load_vi_system_bindings():
+ registry = ConditionalRegistry(Registry(), ViMode())
+ handle = registry.add_binding
-def load_vi_system_bindings(registry, filter=None):
has_focus = filters.HasFocus(SYSTEM_BUFFER)
navigation_mode = ViNavigationMode()
- handle = create_handle_decorator(registry, filter & ViMode())
-
@handle('!', filter=~has_focus & navigation_mode)
def _(event):
"""
@@ -1733,18 +1737,22 @@ def load_vi_system_bindings(registry, filter=None):
# Focus previous buffer again.
event.cli.pop_focus()
+ return registry
+
-def load_vi_search_bindings(registry, get_search_state=None,
- filter=None, search_buffer_name=SEARCH_BUFFER):
+def load_vi_search_bindings(get_search_state=None,
+ search_buffer_name=SEARCH_BUFFER):
assert get_search_state is None or callable(get_search_state)
if not get_search_state:
def get_search_state(cli): return cli.search_state
+ registry = ConditionalRegistry(Registry(), ViMode())
+ handle = registry.add_binding
+
has_focus = filters.HasFocus(search_buffer_name)
navigation_mode = ViNavigationMode()
selection_mode = ViSelectionMode()
- handle = create_handle_decorator(registry, filter & ViMode())
@handle('/', filter=navigation_mode|selection_mode)
@handle(Keys.ControlS, filter=~has_focus)
@@ -1835,13 +1843,16 @@ def load_vi_search_bindings(registry, get_search_state=None,
event.cli.pop_focus()
event.cli.buffers[search_buffer_name].reset()
+ return registry
-def load_extra_vi_page_navigation_bindings(registry, filter=None):
+
+def load_extra_vi_page_navigation_bindings():
"""
Key bindings, for scrolling up and down through pages.
This are separate bindings, because GNU readline doesn't have them.
"""
- handle = create_handle_decorator(registry, filter & ViMode())
+ registry = ConditionalRegistry(Registry(), ViMode())
+ handle = registry.add_binding
handle(Keys.ControlF)(scroll_forward)
handle(Keys.ControlB)(scroll_backward)
@@ -1852,6 +1863,8 @@ def load_extra_vi_page_navigation_bindings(registry, filter=None):
handle(Keys.PageDown)(scroll_page_down)
handle(Keys.PageUp)(scroll_page_up)
+ return registry
+
class ViStateFilter(Filter):
" Deprecated! "
diff --git a/prompt_toolkit/key_binding/defaults.py b/prompt_toolkit/key_binding/defaults.py
new file mode 100644
index 00000000..cd770df6
--- /dev/null
+++ b/prompt_toolkit/key_binding/defaults.py
@@ -0,0 +1,120 @@
+"""
+Default key bindings.::
+
+ registry = load_default_key_bindings()
+ app = Application(key_bindings_registry=registry)
+"""
+from __future__ import unicode_literals
+from prompt_toolkit.key_binding.registry import ConditionalRegistry, MergedRegistry
+from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings, load_abort_and_exit_bindings, load_basic_system_bindings, load_auto_suggestion_bindings, load_mouse_bindings
+from prompt_toolkit.key_binding.bindings.emacs import load_emacs_bindings, load_emacs_system_bindings, load_emacs_search_bindings, load_emacs_open_in_editor_bindings, load_extra_emacs_page_navigation_bindings
+from prompt_toolkit.key_binding.bindings.vi import load_vi_bindings, load_vi_system_bindings, load_vi_search_bindings, load_vi_open_in_editor_bindings, load_extra_vi_page_navigation_bindings
+from prompt_toolkit.filters import to_cli_filter
+
+__all__ = (
+ 'load_default_key_bindings',
+ 'load_default_key_bindings_for_prompt',
+)
+
+
+def load_default_key_bindings(
+ get_search_state=None,
+ enable_abort_and_exit_bindings=False,
+ enable_system_bindings=False,
+ enable_search=False,
+ enable_open_in_editor=False,
+ enable_extra_page_navigation=False,
+ enable_auto_suggest_bindings=False):
+ """
+ Create a Registry object that contains the default key bindings.
+
+ :param enable_abort_and_exit_bindings: Filter to enable Ctrl-C and Ctrl-D.
+ :param enable_system_bindings: Filter to enable the system bindings (meta-!
+ prompt and Control-Z suspension.)
+ :param enable_search: Filter to enable the search bindings.
+ :param enable_open_in_editor: Filter to enable open-in-editor.
+ :param enable_open_in_editor: Filter to enable open-in-editor.
+ :param enable_extra_page_navigation: Filter for enabling extra page
+ navigation. (Bindings for up/down scrolling through long pages, like in
+ Emacs or Vi.)
+ :param enable_auto_suggest_bindings: Filter to enable fish-style suggestions.
+ """
+
+ assert get_search_state is None or callable(get_search_state)
+
+ # Accept both Filters and booleans as input.
+ enable_abort_and_exit_bindings = to_cli_filter(enable_abort_and_exit_bindings)
+ enable_system_bindings = to_cli_filter(enable_system_bindings)
+ enable_search = to_cli_filter(enable_search)
+ enable_open_in_editor = to_cli_filter(enable_open_in_editor)
+ enable_extra_page_navigation = to_cli_filter(enable_extra_page_navigation)
+ enable_auto_suggest_bindings = to_cli_filter(enable_auto_suggest_bindings)
+
+ registry = MergedRegistry([
+ # Load basic bindings.
+ load_basic_bindings(),
+ load_mouse_bindings(),
+
+ ConditionalRegistry(load_abort_and_exit_bindings(),
+ enable_abort_and_exit_bindings),
+
+ ConditionalRegistry(load_basic_system_bindings(),
+ enable_system_bindings),
+
+ # Load emacs bindings.
+ load_emacs_bindings(),
+
+ ConditionalRegistry(load_emacs_open_in_editor_bindings(),
+ enable_open_in_editor),
+
+ ConditionalRegistry(load_emacs_search_bindings(get_search_state=get_search_state),
+ enable_search),
+
+ ConditionalRegistry(load_emacs_system_bindings(),
+ enable_system_bindings),
+
+ ConditionalRegistry(load_extra_emacs_page_navigation_bindings(),
+ enable_extra_page_navigation),
+
+ # Load Vi bindings.
+ load_vi_bindings(enable_visual_key=~enable_open_in_editor,
+ get_search_state=get_search_state),
+
+ ConditionalRegistry(load_vi_open_in_editor_bindings(),
+ enable_open_in_editor),
+
+ ConditionalRegistry(load_vi_search_bindings(get_search_state=get_search_state),
+ enable_search),
+
+ ConditionalRegistry(load_vi_system_bindings(),
+ enable_system_bindings),
+
+ ConditionalRegistry(load_extra_vi_page_navigation_bindings(),
+ enable_extra_page_navigation),
+
+ # Suggestion bindings.
+ # (This has to come at the end, because the Vi bindings also have an
+ # implementation for the "right arrow", but we really want the
+ # suggestion binding when a suggestion is available.)
+ ConditionalRegistry(load_auto_suggestion_bindings(),
+ enable_auto_suggest_bindings),
+ ])
+
+ return registry
+
+
+def load_default_key_bindings_for_prompt(**kw):
+ """
+ Create a ``Registry`` object with the defaults key bindings for an input
+ prompt.
+
+ This activates the key bindings for abort/exit (Ctrl-C/Ctrl-D),
+ incremental search and auto suggestions.
+
+ (Not for full screen applications.)
+ """
+ kw.setdefault('enable_abort_and_exit_bindings', True)
+ kw.setdefault('enable_search', True)
+ kw.setdefault('enable_auto_suggest_bindings', True)
+
+ return load_default_key_bindings(**kw)
diff --git a/prompt_toolkit/key_binding/input_processor.py b/prompt_toolkit/key_binding/input_processor.py
index ffe7ac13..b8d3a1b9 100644
--- a/prompt_toolkit/key_binding/input_processor.py
+++ b/prompt_toolkit/key_binding/input_processor.py
@@ -12,7 +12,7 @@ from prompt_toolkit.filters.cli import ViNavigationMode
from prompt_toolkit.keys import Keys, Key
from prompt_toolkit.utils import Event
-from .registry import Registry
+from .registry import BaseRegistry
from collections import deque
from six.moves import range
@@ -67,11 +67,11 @@ class InputProcessor(object):
# Now the ControlX-ControlC callback will be called if this sequence is
# registered in the registry.
- :param registry: `Registry` instance.
+ :param registry: `BaseRegistry` instance.
:param cli_ref: weakref to `CommandLineInterface`.
"""
def __init__(self, registry, cli_ref):
- assert isinstance(registry, Registry)
+ assert isinstance(registry, BaseRegistry)
self._registry = registry
self._cli_ref = cli_ref
diff --git a/prompt_toolkit/key_binding/manager.py b/prompt_toolkit/key_binding/manager.py
index 286e38ac..c2972e8f 100644
--- a/prompt_toolkit/key_binding/manager.py
+++ b/prompt_toolkit/key_binding/manager.py
@@ -1,4 +1,7 @@
"""
+DEPRECATED:
+Use `prompt_toolkit.key_binding.defaults.load_default_key_bindings` instead.
+
:class:`KeyBindingManager` is a utility (or shortcut) for loading all the key
bindings in a key binding registry, with a logic set of filters to quickly to
quickly change from Vi to Emacs key bindings at runtime.
@@ -8,14 +11,12 @@ You don't have to use this, but it's practical.
Usage::
manager = KeyBindingManager()
- cli = CommandLineInterface(key_bindings_registry=manager.registry)
+ app = Application(key_bindings_registry=manager.registry)
"""
from __future__ import unicode_literals
-from prompt_toolkit.key_binding.registry import Registry
-from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings, load_abort_and_exit_bindings, load_basic_system_bindings, load_auto_suggestion_bindings, load_mouse_bindings
-from prompt_toolkit.key_binding.bindings.emacs import load_emacs_bindings, load_emacs_system_bindings, load_emacs_search_bindings, load_emacs_open_in_editor_bindings, load_extra_emacs_page_navigation_bindings
-from prompt_toolkit.key_binding.bindings.vi import load_vi_bindings, load_vi_system_bindings, load_vi_search_bindings, load_vi_open_in_editor_bindings, load_extra_vi_page_navigation_bindings
+from .defaults import load_default_key_bindings
from prompt_toolkit.filters import to_cli_filter
+from prompt_toolkit.key_binding.registry import Registry, ConditionalRegistry, MergedRegistry
__all__ = (
'KeyBindingManager',
@@ -36,92 +37,40 @@ class KeyBindingManager(object):
:param enable_extra_page_navigation: Filter for enabling extra page navigation.
(Bindings for up/down scrolling through long pages, like in Emacs or Vi.)
:param enable_auto_suggest_bindings: Filter to enable fish-style suggestions.
- :param enable_all: Filter to enable (or disable) all bindings.
:param enable_vi_mode: Deprecated!
"""
- def __init__(self, registry=None,
+ def __init__(self,
+ registry=None, # XXX: not used anymore.
enable_vi_mode=None, # (`enable_vi_mode` is deprecated.)
+ enable_all=True, #
get_search_state=None,
enable_abort_and_exit_bindings=False,
- enable_system_bindings=False, enable_search=False,
- enable_open_in_editor=False, enable_extra_page_navigation=False,
- enable_auto_suggest_bindings=False,
- enable_all=True):
+ enable_system_bindings=False,
+ enable_search=False,
+ enable_open_in_editor=False,
+ enable_extra_page_navigation=False,
+ enable_auto_suggest_bindings=False):
assert registry is None or isinstance(registry, Registry)
assert get_search_state is None or callable(get_search_state)
-
- # Create registry.
- self.registry = registry or Registry()
-
- # Accept both Filters and booleans as input.
- enable_abort_and_exit_bindings = to_cli_filter(enable_abort_and_exit_bindings)
- enable_system_bindings = to_cli_filter(enable_system_bindings)
- enable_search = to_cli_filter(enable_search)
- enable_open_in_editor = to_cli_filter(enable_open_in_editor)
- enable_extra_page_navigation = to_cli_filter(enable_extra_page_navigation)
- enable_auto_suggest_bindings = to_cli_filter(enable_auto_suggest_bindings)
enable_all = to_cli_filter(enable_all)
- # Load basic bindings.
- load_basic_bindings(self.registry, enable_all)
- load_mouse_bindings(self.registry, enable_all)
-
- load_abort_and_exit_bindings(
- self.registry, enable_abort_and_exit_bindings & enable_all)
-
- load_basic_system_bindings(self.registry,
- enable_system_bindings & enable_all)
-
- # Load emacs bindings.
- load_emacs_bindings(self.registry, enable_all)
-
- load_emacs_open_in_editor_bindings(
- self.registry, enable_open_in_editor & enable_all)
-
- load_emacs_search_bindings(
- self.registry,
- filter=enable_search & enable_all,
- get_search_state=get_search_state)
-
- load_emacs_system_bindings(
- self.registry, enable_system_bindings & enable_all)
-
- load_extra_emacs_page_navigation_bindings(
- self.registry,
- enable_extra_page_navigation & enable_all)
-
- # Load Vi bindings.
- load_vi_bindings(
- self.registry, enable_visual_key=~enable_open_in_editor,
- filter=enable_all,
- get_search_state=get_search_state)
-
- load_vi_open_in_editor_bindings(
- self.registry,
- enable_open_in_editor & enable_all)
-
- load_vi_search_bindings(
- self.registry,
- filter=enable_search & enable_all,
- get_search_state=get_search_state)
-
- load_vi_system_bindings(
- self.registry,
- enable_system_bindings & enable_all)
-
- load_extra_vi_page_navigation_bindings(
- self.registry,
- enable_extra_page_navigation & enable_all)
-
- # Suggestion bindings.
- # (This has to come at the end, because the Vi bindings also have an
- # implementation for the "right arrow", but we really want the
- # suggestion binding when a suggestion is available.)
- load_auto_suggestion_bindings(
- self.registry,
- enable_auto_suggest_bindings & enable_all)
+ defaults = load_default_key_bindings(
+ get_search_state=get_search_state,
+ enable_abort_and_exit_bindings=enable_abort_and_exit_bindings,
+ enable_system_bindings=enable_system_bindings,
+ enable_search=enable_search,
+ enable_open_in_editor=enable_open_in_editor,
+ enable_extra_page_navigation=enable_extra_page_navigation,
+ enable_auto_suggest_bindings=enable_auto_suggest_bindings)
+
+ # Note, we wrap this whole thing again in a MergedRegistry, because we
+ # don't want the `enable_all` settings to apply on items that were
+ # added to the registry as a whole.
+ self.registry = MergedRegistry([
+ ConditionalRegistry(defaults, enable_all)
+ ])
@classmethod
def for_prompt(cls, **kw):
diff --git a/prompt_toolkit/key_binding/registry.py b/prompt_toolkit/key_binding/registry.py
index 1ac3164b..3bbb7dea 100644
--- a/prompt_toolkit/key_binding/registry.py
+++ b/prompt_toolkit/key_binding/registry.py
@@ -1,12 +1,42 @@
+"""
+Key bindings registry.
+
+A `Registry` object is a container that holds a list of key bindings. It has a
+very efficient internal data structure for checking which key bindings apply
+for a pressed key.
+
+Typical usage::
+
+ r = Registry()
+
+ @r.add_binding(Keys.ControlX, Keys.ControlC, filter=INSERT)
+ def handler(event):
+ # Handle ControlX-ControlC key sequence.
+ pass
+
+
+It is also possible to combine multiple registries. We do this in the default
+key bindings. There are some registries that contain Emacs bindings, while
+others contain the Vi bindings. They are merged together using a
+`MergedRegistry`.
+
+We also have a `ConditionalRegistry` object that can enable/disable a group of
+key bindings at once.
+"""
from __future__ import unicode_literals
+from abc import ABCMeta, abstractmethod
+
from prompt_toolkit.cache import SimpleCache
from prompt_toolkit.filters import CLIFilter, to_cli_filter, Never
from prompt_toolkit.keys import Key, Keys
-from six import text_type
+from six import text_type, with_metaclass
__all__ = (
+ 'BaseRegistry',
'Registry',
+ 'ConditionalRegistry',
+ 'MergedRegistry',
)
@@ -35,25 +65,39 @@ class _Binding(object):
self.__class__.__name__, self.keys, self.handler)
-class Registry(object):
+class BaseRegistry(with_metaclass(ABCMeta, object)):
"""
- Key binding registry.
+ Interface for a Registry.
+ """
+ @abstractmethod
+ def add_binding(self, *keys, **kwargs):
+ pass
+
+ @abstractmethod
+ def remove_binding(self, function):
+ pass
+
+ @abstractmethod
+ def get_bindings_for_keys(self, keys):
+ pass
- ::
+ @abstractmethod
+ def get_bindings_starting_with_keys(self, keys):
+ pass
- r = Registry()
- @r.add_binding(Keys.ControlX, Keys.ControlC, filter=INSERT)
- def handler(event):
- # Handle ControlX-ControlC key sequence.
- pass
+class Registry(BaseRegistry):
+ """
+ Key binding registry.
"""
def __init__(self):
self.key_bindings = []
self._get_bindings_for_keys_cache = SimpleCache(maxsize=10000)
self._get_bindings_starting_with_keys_cache = SimpleCache(maxsize=1000)
+ self._version = 0 # For cache invalidation.
def _clear_cache(self):
+ self._version += 1
self._get_bindings_for_keys_cache.clear()
self._get_bindings_starting_with_keys_cache.clear()
@@ -174,3 +218,136 @@ class Registry(object):
return result
return self._get_bindings_starting_with_keys_cache.get(keys, get)
+
+
+class _AddRemoveMixin(BaseRegistry):
+ """
+ Common part for ConditionalRegistry and MergedRegistry.
+ """
+ def __init__(self):
+ # `Registry` to be synchronized with all the others.
+ self._registry2 = Registry()
+ self._last_version = None
+
+ # The 'extra' registry. Mostly for backwards compatibility.
+ self._extra_registry = Registry()
+
+ def _update_cache(self):
+ raise NotImplementedError
+
+ # For backwards, compatibility, we allow adding bindings to both
+ # ConditionalRegistry and MergedRegistry. This is however not the
+ # recommended way. Better is to create a new registry and merge them
+ # together using MergedRegistry.
+
+ def add_binding(self, *k, **kw):
+ return self._extra_registry.add_binding(*k, **kw)
+
+ def remove_binding(self, *k, **kw):
+ return self._extra_registry.remove_binding(*k, **kw)
+
+ # Proxy methods to self._registry2.
+
+ @property
+ def key_bindings(self):
+ self._update_cache()
+ return self._registry2.key_bindings
+
+ @property
+ def _version(self):
+ self._update_cache()
+ return self._last_version
+
+ def get_bindings_for_keys(self, *a, **kw):
+ self._update_cache()
+ return self._registry2.get_bindings_for_keys(*a, **kw)
+
+ def get_bindings_starting_with_keys(self, *a, **kw):
+ self._update_cache()
+ return self._registry2.get_bindings_starting_with_keys(*a, **kw)
+
+
+class ConditionalRegistry(_AddRemoveMixin):
+ """
+ Wraps around a `Registry`. Disable/enable all the key bindings according to
+ the given (additional) filter.::
+
+ @Condition
+ def setting_is_true(cli):