diff options
author | Jonathan Slenders <jonathan@slenders.be> | 2016-12-17 15:26:45 +0100 |
---|---|---|
committer | Jonathan Slenders <jonathan@slenders.be> | 2016-12-17 15:26:49 +0100 |
commit | 17047496500480fc2c7653e701da0fdd5e08bfd4 (patch) | |
tree | 3f26f0ff68dba4026c72e2be02ae728ef60abbff | |
parent | b3e0f6c842cc1f92530fa700097d3599faa37877 (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.py | 14 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/bindings/basic.py | 42 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/bindings/emacs.py | 50 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/bindings/utils.py | 25 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/bindings/vi.py | 47 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/defaults.py | 120 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/input_processor.py | 6 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/manager.py | 109 | ||||
-rw-r--r-- | prompt_toolkit/key_binding/registry.py | 195 | ||||
-rw-r--r-- | prompt_toolkit/shortcuts.py | 6 |
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): |