diff options
author | nkozlovskiy <[email protected]> | 2023-09-29 12:24:06 +0300 |
---|---|---|
committer | nkozlovskiy <[email protected]> | 2023-09-29 12:41:34 +0300 |
commit | e0e3e1717e3d33762ce61950504f9637a6e669ed (patch) | |
tree | bca3ff6939b10ed60c3d5c12439963a1146b9711 /contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding | |
parent | 38f2c5852db84c7b4d83adfcb009eb61541d1ccd (diff) |
add ydb deps
Diffstat (limited to 'contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding')
15 files changed, 6088 insertions, 0 deletions
diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/__init__.py new file mode 100644 index 00000000000..baffc488252 --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/__init__.py @@ -0,0 +1 @@ +from __future__ import unicode_literals diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/__init__.py new file mode 100644 index 00000000000..e69de29bb2d --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/__init__.py diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/basic.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/basic.py new file mode 100644 index 00000000000..401135dec06 --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/basic.py @@ -0,0 +1,407 @@ +# pylint: disable=function-redefined +from __future__ import unicode_literals + +from prompt_toolkit.enums import DEFAULT_BUFFER +from prompt_toolkit.filters import HasSelection, Condition, EmacsInsertMode, ViInsertMode +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout.screen import Point +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 .named_commands import get_by_name +from ..registry import Registry + + +__all__ = ( + 'load_basic_bindings', + 'load_abort_and_exit_bindings', + 'load_basic_system_bindings', + 'load_auto_suggestion_bindings', +) + +def if_no_repeat(event): + """ Callable that returns True when the previous event was delivered to + another handler. """ + return not event.is_repeat + + +def load_basic_bindings(): + registry = Registry() + insert_mode = ViInsertMode() | EmacsInsertMode() + handle = registry.add_binding + has_selection = HasSelection() + + @handle(Keys.ControlA) + @handle(Keys.ControlB) + @handle(Keys.ControlC) + @handle(Keys.ControlD) + @handle(Keys.ControlE) + @handle(Keys.ControlF) + @handle(Keys.ControlG) + @handle(Keys.ControlH) + @handle(Keys.ControlI) + @handle(Keys.ControlJ) + @handle(Keys.ControlK) + @handle(Keys.ControlL) + @handle(Keys.ControlM) + @handle(Keys.ControlN) + @handle(Keys.ControlO) + @handle(Keys.ControlP) + @handle(Keys.ControlQ) + @handle(Keys.ControlR) + @handle(Keys.ControlS) + @handle(Keys.ControlT) + @handle(Keys.ControlU) + @handle(Keys.ControlV) + @handle(Keys.ControlW) + @handle(Keys.ControlX) + @handle(Keys.ControlY) + @handle(Keys.ControlZ) + @handle(Keys.F1) + @handle(Keys.F2) + @handle(Keys.F3) + @handle(Keys.F4) + @handle(Keys.F5) + @handle(Keys.F6) + @handle(Keys.F7) + @handle(Keys.F8) + @handle(Keys.F9) + @handle(Keys.F10) + @handle(Keys.F11) + @handle(Keys.F12) + @handle(Keys.F13) + @handle(Keys.F14) + @handle(Keys.F15) + @handle(Keys.F16) + @handle(Keys.F17) + @handle(Keys.F18) + @handle(Keys.F19) + @handle(Keys.F20) + @handle(Keys.ControlSpace) + @handle(Keys.ControlBackslash) + @handle(Keys.ControlSquareClose) + @handle(Keys.ControlCircumflex) + @handle(Keys.ControlUnderscore) + @handle(Keys.Backspace) + @handle(Keys.Up) + @handle(Keys.Down) + @handle(Keys.Right) + @handle(Keys.Left) + @handle(Keys.ShiftUp) + @handle(Keys.ShiftDown) + @handle(Keys.ShiftRight) + @handle(Keys.ShiftLeft) + @handle(Keys.Home) + @handle(Keys.End) + @handle(Keys.Delete) + @handle(Keys.ShiftDelete) + @handle(Keys.ControlDelete) + @handle(Keys.PageUp) + @handle(Keys.PageDown) + @handle(Keys.BackTab) + @handle(Keys.Tab) + @handle(Keys.ControlLeft) + @handle(Keys.ControlRight) + @handle(Keys.ControlUp) + @handle(Keys.ControlDown) + @handle(Keys.Insert) + @handle(Keys.Ignore) + def _(event): + """ + First, for any of these keys, Don't do anything by default. Also don't + catch them in the 'Any' handler which will insert them as data. + + If people want to insert these characters as a literal, they can always + do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi + mode.) + """ + pass + + # Readline-style bindings. + handle(Keys.Home)(get_by_name('beginning-of-line')) + handle(Keys.End)(get_by_name('end-of-line')) + handle(Keys.Left)(get_by_name('backward-char')) + handle(Keys.Right)(get_by_name('forward-char')) + handle(Keys.ControlUp)(get_by_name('previous-history')) + handle(Keys.ControlDown)(get_by_name('next-history')) + handle(Keys.ControlL)(get_by_name('clear-screen')) + + handle(Keys.ControlK, filter=insert_mode)(get_by_name('kill-line')) + handle(Keys.ControlU, filter=insert_mode)(get_by_name('unix-line-discard')) + handle(Keys.ControlH, filter=insert_mode, save_before=if_no_repeat)( + get_by_name('backward-delete-char')) + handle(Keys.Backspace, filter=insert_mode, save_before=if_no_repeat)( + get_by_name('backward-delete-char')) + handle(Keys.Delete, filter=insert_mode, save_before=if_no_repeat)( + get_by_name('delete-char')) + handle(Keys.ShiftDelete, filter=insert_mode, save_before=if_no_repeat)( + get_by_name('delete-char')) + handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)( + get_by_name('self-insert')) + handle(Keys.ControlT, filter=insert_mode)(get_by_name('transpose-chars')) + handle(Keys.ControlW, filter=insert_mode)(get_by_name('unix-word-rubout')) + handle(Keys.ControlI, filter=insert_mode)(get_by_name('menu-complete')) + handle(Keys.BackTab, filter=insert_mode)(get_by_name('menu-complete-backward')) + + handle(Keys.PageUp, filter= ~has_selection)(get_by_name('previous-history')) + handle(Keys.PageDown, filter= ~has_selection)(get_by_name('next-history')) + + # CTRL keys. + + text_before_cursor = Condition(lambda cli: cli.current_buffer.text) + handle(Keys.ControlD, filter=text_before_cursor & insert_mode)(get_by_name('delete-char')) + + is_multiline = Condition(lambda cli: cli.current_buffer.is_multiline()) + is_returnable = Condition(lambda cli: cli.current_buffer.accept_action.is_returnable) + + @handle(Keys.ControlJ, filter=is_multiline & insert_mode) + def _(event): + " Newline (in case of multiline input. " + event.current_buffer.newline(copy_margin=not event.cli.in_paste_mode) + + @handle(Keys.ControlJ, filter=~is_multiline & is_returnable) + def _(event): + " Enter, accept input. " + buff = event.current_buffer + buff.accept_action.validate_and_handle(event.cli, buff) + + # Delete the word before the cursor. + + @handle(Keys.Up) + def _(event): + event.current_buffer.auto_up(count=event.arg) + + @handle(Keys.Down) + def _(event): + event.current_buffer.auto_down(count=event.arg) + + @handle(Keys.Delete, filter=has_selection) + def _(event): + data = event.current_buffer.cut_selection() + event.cli.clipboard.set_data(data) + + # Global bindings. + + @handle(Keys.ControlZ) + def _(event): + """ + By default, control-Z should literally insert Ctrl-Z. + (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File. + In a Python REPL for instance, it's possible to type + Control-Z followed by enter to quit.) + + When the system bindings are loaded and suspend-to-background is + supported, that will override this binding. + """ + event.current_buffer.insert_text(event.data) + + @handle(Keys.CPRResponse, save_before=lambda e: False) + def _(event): + """ + Handle incoming Cursor-Position-Request response. + """ + # The incoming data looks like u'\x1b[35;1R' + # Parse row/col information. + row, col = map(int, event.data[2:-1].split(';')) + + # Report absolute cursor position to the renderer. + event.cli.renderer.report_absolute_cursor_row(row) + + @handle(Keys.BracketedPaste) + def _(event): + " Pasting from clipboard. " + data = event.data + + # Be sure to use \n as line ending. + # Some terminals (Like iTerm2) seem to paste \r\n line endings in a + # bracketed paste. See: https://github.com/ipython/ipython/issues/9737 + data = data.replace('\r\n', '\n') + data = data.replace('\r', '\n') + + event.current_buffer.insert_text(data) + + @handle(Keys.Any, filter=Condition(lambda cli: cli.quoted_insert), eager=True) + def _(event): + """ + Handle quoted insert. + """ + event.current_buffer.insert_text(event.data, overwrite=False) + event.cli.quoted_insert = False + + return registry + + +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): + """ + Handling of incoming mouse event. + """ + # Typical: "Esc[MaB*" + # Urxvt: "Esc[96;14;13M" + # Xterm SGR: "Esc[<64;85;12M" + + # Parse incoming packet. + if event.data[2] == 'M': + # Typical. + mouse_event, x, y = map(ord, event.data[3:]) + mouse_event = { + 32: MouseEventType.MOUSE_DOWN, + 35: MouseEventType.MOUSE_UP, + 96: MouseEventType.SCROLL_UP, + 97: MouseEventType.SCROLL_DOWN, + }.get(mouse_event) + + # Handle situations where `PosixStdinReader` used surrogateescapes. + if x >= 0xdc00: x-= 0xdc00 + if y >= 0xdc00: y-= 0xdc00 + + x -= 32 + y -= 32 + else: + # Urxvt and Xterm SGR. + # When the '<' is not present, we are not using the Xterm SGR mode, + # but Urxvt instead. + data = event.data[2:] + if data[:1] == '<': + sgr = True + data = data[1:] + else: + sgr = False + + # Extract coordinates. + mouse_event, x, y = map(int, data[:-1].split(';')) + m = data[-1] + + # Parse event type. + if sgr: + mouse_event = { + (0, 'M'): MouseEventType.MOUSE_DOWN, + (0, 'm'): MouseEventType.MOUSE_UP, + (64, 'M'): MouseEventType.SCROLL_UP, + (65, 'M'): MouseEventType.SCROLL_DOWN, + }.get((mouse_event, m)) + else: + mouse_event = { + 32: MouseEventType.MOUSE_DOWN, + 35: MouseEventType.MOUSE_UP, + 96: MouseEventType.SCROLL_UP, + 97: MouseEventType.SCROLL_DOWN, + }.get(mouse_event) + + x -= 1 + y -= 1 + + # Only handle mouse events when we know the window height. + if event.cli.renderer.height_is_known and mouse_event is not None: + # Take region above the layout into account. The reported + # coordinates are absolute to the visible part of the terminal. + try: + y -= event.cli.renderer.rows_above_layout + except HeightIsUnknownError: + return + + # Call the mouse handler from the renderer. + handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] + handler(event.cli, MouseEvent(position=Point(x=x, y=y), + event_type=mouse_event)) + + @registry.add_binding(Keys.WindowsMouseEvent) + def _(event): + """ + Handling of mouse events for Windows. + """ + assert is_windows() # This key binding should only exist for Windows. + + # Parse data. + event_type, x, y = event.data.split(';') + x = int(x) + y = int(y) + + # Make coordinates absolute to the visible part of the terminal. + screen_buffer_info = event.cli.renderer.output.get_win32_screen_buffer_info() + rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.cli.renderer._cursor_pos.y + y -= rows_above_cursor + + # Call the mouse event handler. + handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] + handler(event.cli, MouseEvent(position=Point(x=x, y=y), + event_type=event_type)) + + return registry + + +def load_abort_and_exit_bindings(): + """ + Basic bindings for abort (Ctrl-C) and exit (Ctrl-D). + """ + registry = Registry() + handle = registry.add_binding + + @handle(Keys.ControlC) + def _(event): + " Abort when Control-C has been pressed. " + event.cli.abort() + + @Condition + def ctrl_d_condition(cli): + """ Ctrl-D binding is only active when the default buffer is selected + and empty. """ + return (cli.current_buffer_name == DEFAULT_BUFFER and + not cli.current_buffer.text) + + handle(Keys.ControlD, filter=ctrl_d_condition)(get_by_name('end-of-file')) + + return registry + + +def load_basic_system_bindings(): + """ + Basic system bindings (For both Emacs and Vi mode.) + """ + registry = Registry() + + suspend_supported = Condition( + lambda cli: suspend_to_background_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(): + """ + Key bindings for accepting auto suggestion text. + """ + registry = Registry() + handle = registry.add_binding + + suggestion_available = Condition( + lambda cli: + cli.current_buffer.suggestion is not None and + cli.current_buffer.document.is_cursor_at_the_end) + + @handle(Keys.ControlF, filter=suggestion_available) + @handle(Keys.ControlE, filter=suggestion_available) + @handle(Keys.Right, filter=suggestion_available) + def _(event): + " Accept suggestion. " + b = event.current_buffer + suggestion = b.suggestion + + if suggestion: + b.insert_text(suggestion.text) + + return registry diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/completion.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/completion.py new file mode 100644 index 00000000000..4903900bc6e --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/completion.py @@ -0,0 +1,161 @@ +""" +Key binding handlers for displaying completions. +""" +from __future__ import unicode_literals +from prompt_toolkit.completion import CompleteEvent, get_common_complete_suffix +from prompt_toolkit.utils import get_cwidth +from prompt_toolkit.keys import Keys +from prompt_toolkit.key_binding.registry import Registry + +import math + +__all__ = ( + 'generate_completions', + 'display_completions_like_readline', +) + +def generate_completions(event): + r""" + Tab-completion: where the first tab completes the common suffix and the + second tab lists all the completions. + """ + b = event.current_buffer + + # When already navigating through completions, select the next one. + if b.complete_state: + b.complete_next() + else: + event.cli.start_completion(insert_common_part=True, select_first=False) + + +def display_completions_like_readline(event): + """ + Key binding handler for readline-style tab completion. + This is meant to be as similar as possible to the way how readline displays + completions. + + Generate the completions immediately (blocking) and display them above the + prompt in columns. + + Usage:: + + # Call this handler when 'Tab' has been pressed. + registry.add_binding(Keys.ControlI)(display_completions_like_readline) + """ + # Request completions. + b = event.current_buffer + if b.completer is None: + return + complete_event = CompleteEvent(completion_requested=True) + completions = list(b.completer.get_completions(b.document, complete_event)) + + # Calculate the common suffix. + common_suffix = get_common_complete_suffix(b.document, completions) + + # One completion: insert it. + if len(completions) == 1: + b.delete_before_cursor(-completions[0].start_position) + b.insert_text(completions[0].text) + # Multiple completions with common part. + elif common_suffix: + b.insert_text(common_suffix) + # Otherwise: display all completions. + elif completions: + _display_completions_like_readline(event.cli, completions) + + +def _display_completions_like_readline(cli, completions): + """ + Display the list of completions in columns above the prompt. + This will ask for a confirmation if there are too many completions to fit + on a single page and provide a paginator to walk through them. + """ + from prompt_toolkit.shortcuts import create_confirm_application + assert isinstance(completions, list) + + # Get terminal dimensions. + term_size = cli.output.get_size() + term_width = term_size.columns + term_height = term_size.rows + + # Calculate amount of required columns/rows for displaying the + # completions. (Keep in mind that completions are displayed + # alphabetically column-wise.) + max_compl_width = min(term_width, + max(get_cwidth(c.text) for c in completions) + 1) + column_count = max(1, term_width // max_compl_width) + completions_per_page = column_count * (term_height - 1) + page_count = int(math.ceil(len(completions) / float(completions_per_page))) + # Note: math.ceil can return float on Python2. + + def display(page): + # Display completions. + page_completions = completions[page * completions_per_page: + (page+1) * completions_per_page] + + page_row_count = int(math.ceil(len(page_completions) / float(column_count))) + page_columns = [page_completions[i * page_row_count:(i+1) * page_row_count] + for i in range(column_count)] + + result = [] + for r in range(page_row_count): + for c in range(column_count): + try: + result.append(page_columns[c][r].text.ljust(max_compl_width)) + except IndexError: + pass + result.append('\n') + cli.output.write(''.join(result)) + cli.output.flush() + + # User interaction through an application generator function. + def run(): + if len(completions) > completions_per_page: + # Ask confirmation if it doesn't fit on the screen. + message = 'Display all {} possibilities? (y on n) '.format(len(completions)) + confirm = yield create_confirm_application(message) + + if confirm: + # Display pages. + for page in range(page_count): + display(page) + + if page != page_count - 1: + # Display --MORE-- and go to the next page. + show_more = yield _create_more_application() + if not show_more: + return + else: + cli.output.write('\n'); cli.output.flush() + else: + # Display all completions. + display(0) + + cli.run_application_generator(run, render_cli_done=True) + + +def _create_more_application(): + """ + Create an `Application` instance that displays the "--MORE--". + """ + from prompt_toolkit.shortcuts import create_prompt_application + registry = Registry() + + @registry.add_binding(' ') + @registry.add_binding('y') + @registry.add_binding('Y') + @registry.add_binding(Keys.ControlJ) + @registry.add_binding(Keys.ControlI) # Tab. + def _(event): + event.cli.set_return_value(True) + + @registry.add_binding('n') + @registry.add_binding('N') + @registry.add_binding('q') + @registry.add_binding('Q') + @registry.add_binding(Keys.ControlC) + def _(event): + event.cli.set_return_value(False) + + return create_prompt_application( + '--MORE--', key_bindings_registry=registry, erase_when_done=True) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/emacs.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/emacs.py new file mode 100644 index 00000000000..bccdb04ff38 --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/emacs.py @@ -0,0 +1,452 @@ +# pylint: disable=function-redefined +from __future__ import unicode_literals +from prompt_toolkit.buffer import SelectionType, indent, unindent +from prompt_toolkit.keys import Keys +from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER +from prompt_toolkit.filters import Condition, EmacsMode, HasSelection, EmacsInsertMode, HasFocus, HasArg +from prompt_toolkit.completion import CompleteEvent + +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', + 'load_emacs_search_bindings', + 'load_emacs_system_bindings', + 'load_extra_emacs_page_navigation_bindings', +) + + +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 + registry = ConditionalRegistry(Registry(), EmacsMode()) + handle = registry.add_binding + + insert_mode = EmacsInsertMode() + has_selection = HasSelection() + + @handle(Keys.Escape) + def _(event): + """ + By default, ignore escape key. + + (If we don't put this here, and Esc is followed by a key which sequence + is not handled, we'll insert an Escape character in the input stream. + Something we don't want and happens to easily in emacs mode. + Further, people can always use ControlQ to do a quoted insert.) + """ + pass + + handle(Keys.ControlA)(get_by_name('beginning-of-line')) + handle(Keys.ControlB)(get_by_name('backward-char')) + handle(Keys.ControlDelete, filter=insert_mode)(get_by_name('kill-word')) + handle(Keys.ControlE)(get_by_name('end-of-line')) + handle(Keys.ControlF)(get_by_name('forward-char')) + handle(Keys.ControlLeft)(get_by_name('backward-word')) + handle(Keys.ControlRight)(get_by_name('forward-word')) + handle(Keys.ControlX, 'r', 'y', filter=insert_mode)(get_by_name('yank')) + handle(Keys.ControlY, filter=insert_mode)(get_by_name('yank')) + handle(Keys.Escape, 'b')(get_by_name('backward-word')) + handle(Keys.Escape, 'c', filter=insert_mode)(get_by_name('capitalize-word')) + handle(Keys.Escape, 'd', filter=insert_mode)(get_by_name('kill-word')) + handle(Keys.Escape, 'f')(get_by_name('forward-word')) + handle(Keys.Escape, 'l', filter=insert_mode)(get_by_name('downcase-word')) + handle(Keys.Escape, 'u', filter=insert_mode)(get_by_name('uppercase-word')) + handle(Keys.Escape, 'y', filter=insert_mode)(get_by_name('yank-pop')) + handle(Keys.Escape, Keys.ControlH, filter=insert_mode)(get_by_name('backward-kill-word')) + handle(Keys.Escape, Keys.Backspace, filter=insert_mode)(get_by_name('backward-kill-word')) + handle(Keys.Escape, '\\', filter=insert_mode)(get_by_name('delete-horizontal-space')) + + handle(Keys.ControlUnderscore, save_before=(lambda e: False), filter=insert_mode)( + get_by_name('undo')) + + handle(Keys.ControlX, Keys.ControlU, save_before=(lambda e: False), filter=insert_mode)( + get_by_name('undo')) + + + handle(Keys.Escape, '<', filter= ~has_selection)(get_by_name('beginning-of-history')) + handle(Keys.Escape, '>', filter= ~has_selection)(get_by_name('end-of-history')) + + handle(Keys.Escape, '.', filter=insert_mode)(get_by_name('yank-last-arg')) + handle(Keys.Escape, '_', filter=insert_mode)(get_by_name('yank-last-arg')) + handle(Keys.Escape, Keys.ControlY, filter=insert_mode)(get_by_name('yank-nth-arg')) + handle(Keys.Escape, '#', filter=insert_mode)(get_by_name('insert-comment')) + handle(Keys.ControlO)(get_by_name('operate-and-get-next')) + + # ControlQ does a quoted insert. Not that for vt100 terminals, you have to + # disable flow control by running ``stty -ixon``, otherwise Ctrl-Q and + # Ctrl-S are captured by the terminal. + handle(Keys.ControlQ, filter= ~has_selection)(get_by_name('quoted-insert')) + + handle(Keys.ControlX, '(')(get_by_name('start-kbd-macro')) + handle(Keys.ControlX, ')')(get_by_name('end-kbd-macro')) + handle(Keys.ControlX, 'e')(get_by_name('call-last-kbd-macro')) + + @handle(Keys.ControlN) + def _(event): + " Next line. " + event.current_buffer.auto_down() + + @handle(Keys.ControlP) + def _(event): + " Previous line. " + event.current_buffer.auto_up(count=event.arg) + + def handle_digit(c): + """ + Handle input of arguments. + The first number needs to be preceeded by escape. + """ + @handle(c, filter=HasArg()) + @handle(Keys.Escape, c) + def _(event): + event.append_to_arg_count(c) + + for c in '0123456789': + handle_digit(c) + + @handle(Keys.Escape, '-', filter=~HasArg()) + def _(event): + """ + """ + if event._arg is None: + event.append_to_arg_count('-') + + @handle('-', filter=Condition(lambda cli: cli.input_processor.arg == '-')) + def _(event): + """ + When '-' is typed again, after exactly '-' has been given as an + argument, ignore this. + """ + event.cli.input_processor.arg = '-' + + is_returnable = Condition( + lambda cli: cli.current_buffer.accept_action.is_returnable) + + # Meta + Newline: always accept input. + handle(Keys.Escape, Keys.ControlJ, filter=insert_mode & is_returnable)( + get_by_name('accept-line')) + + def character_search(buff, char, count): + if count < 0: + match = buff.document.find_backwards(char, in_current_line=True, count=-count) + else: + match = buff.document.find(char, in_current_line=True, count=count) + + if match is not None: + buff.cursor_position += match + + @handle(Keys.ControlSquareClose, Keys.Any) + def _(event): + " When Ctl-] + a character is pressed. go to that character. " + # Also named 'character-search' + character_search(event.current_buffer, event.data, event.arg) + + @handle(Keys.Escape, Keys.ControlSquareClose, Keys.Any) + def _(event): + " Like Ctl-], but backwards. " + # Also named 'character-search-backward' + character_search(event.current_buffer, event.data, -event.arg) + + @handle(Keys.Escape, 'a') + def _(event): + " Previous sentence. " + # TODO: + + @handle(Keys.Escape, 'e') + def _(event): + " Move to end of sentence. " + # TODO: + + @handle(Keys.Escape, 't', filter=insert_mode) + def _(event): + """ + Swap the last two words before the cursor. + """ + # TODO + + @handle(Keys.Escape, '*', filter=insert_mode) + def _(event): + """ + `meta-*`: Insert all possible completions of the preceding text. + """ + buff = event.current_buffer + + # List all completions. + complete_event = CompleteEvent(text_inserted=False, completion_requested=True) + completions = list(buff.completer.get_completions(buff.document, complete_event)) + + # Insert them. + text_to_insert = ' '.join(c.text for c in completions) + buff.insert_text(text_to_insert) + + @handle(Keys.ControlX, Keys.ControlX) + def _(event): + """ + Move cursor back and forth between the start and end of the current + line. + """ + buffer = event.current_buffer + + if buffer.document.is_cursor_at_the_end_of_line: + buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=False) + else: + buffer.cursor_position += buffer.document.get_end_of_line_position() + + @handle(Keys.ControlSpace) + def _(event): + """ + Start of the selection (if the current buffer is not empty). + """ + # Take the current cursor position as the start of this selection. + buff = event.current_buffer + if buff.text: + buff.start_selection(selection_type=SelectionType.CHARACTERS) + + @handle(Keys.ControlG, filter= ~has_selection) + def _(event): + """ + Control + G: Cancel completion menu and validation state. + """ + event.current_buffer.complete_state = None + event.current_buffer.validation_error = None + + @handle(Keys.ControlG, filter=has_selection) + def _(event): + """ + Cancel selection. + """ + event.current_buffer.exit_selection() + + @handle(Keys.ControlW, filter=has_selection) + @handle(Keys.ControlX, 'r', 'k', filter=has_selection) + def _(event): + """ + Cut selected text. + """ + data = event.current_buffer.cut_selection() + event.cli.clipboard.set_data(data) + + @handle(Keys.Escape, 'w', filter=has_selection) + def _(event): + """ + Copy selected text. + """ + data = event.current_buffer.copy_selection() + event.cli.clipboard.set_data(data) + + @handle(Keys.Escape, Keys.Left) + def _(event): + """ + Cursor to start of previous word. + """ + buffer = event.current_buffer + buffer.cursor_position += buffer.document.find_previous_word_beginning(count=event.arg) or 0 + + @handle(Keys.Escape, Keys.Right) + def _(event): + """ + Cursor to start of next word. + """ + buffer = event.current_buffer + buffer.cursor_position += buffer.document.find_next_word_beginning(count=event.arg) or \ + buffer.document.get_end_of_document_position() + + @handle(Keys.Escape, '/', filter=insert_mode) + def _(event): + """ + M-/: Complete. + """ + b = event.current_buffer + if b.complete_state: + b.complete_next() + else: + event.cli.start_completion(select_first=True) + + @handle(Keys.ControlC, '>', filter=has_selection) + def _(event): + """ + Indent selected text. + """ + buffer = event.current_buffer + + buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) + + from_, to = buffer.document.selection_range() + from_, _ = buffer.document.translate_index_to_position(from_) + to, _ = buffer.document.translate_index_to_position(to) + + indent(buffer, from_, to + 1, count=event.arg) + + @handle(Keys.ControlC, '<', filter=has_selection) + def _(event): + """ + Unindent selected text. + """ + buffer = event.current_buffer + + from_, to = buffer.document.selection_range() + from_, _ = buffer.document.translate_index_to_position(from_) + to, _ = buffer.document.translate_index_to_position(to) + + unindent(buffer, from_, to + 1, count=event.arg) + + return registry + + +def load_emacs_open_in_editor_bindings(): + """ + Pressing C-X C-E will open the buffer in an external editor. + """ + registry = Registry() + + 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 + + has_focus = HasFocus(SYSTEM_BUFFER) + + @handle(Keys.Escape, '!', filter= ~has_focus) + def _(event): + """ + M-'!' opens the system prompt. + """ + event.cli.push_focus(SYSTEM_BUFFER) + + @handle(Keys.Escape, filter=has_focus) + @handle(Keys.ControlG, filter=has_focus) + @handle(Keys.ControlC, filter=has_focus) + def _(event): + """ + Cancel system prompt. + """ + event.cli.buffers[SYSTEM_BUFFER].reset() + event.cli.pop_focus() + + @handle(Keys.ControlJ, filter=has_focus) + def _(event): + """ + Run system command. + """ + system_line = event.cli.buffers[SYSTEM_BUFFER] + event.cli.run_system_command(system_line.text) + system_line.reset(append_to_history=True) + + # Focus previous buffer again. + event.cli.pop_focus() + + return registry + + +def load_emacs_search_bindings(get_search_state=None): + registry = ConditionalRegistry(Registry(), EmacsMode()) + handle = registry.add_binding + + has_focus = HasFocus(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 + + @handle(Keys.ControlG, filter=has_focus) + @handle(Keys.ControlC, filter=has_focus) + # NOTE: the reason for not also binding Escape to this one, is that we want + # Alt+Enter to accept input directly in incremental search mode. + def _(event): + """ + Abort an incremental search and restore the original line. + """ + search_buffer = event.cli.buffers[SEARCH_BUFFER] + + search_buffer.reset() + event.cli.pop_focus() + + @handle(Keys.ControlJ, filter=has_focus) + @handle(Keys.Escape, filter=has_focus, eager=True) + def _(event): + """ + When enter pressed in isearch, quit isearch mode. (Multiline + isearch would be too complicated.) + """ + input_buffer = event.cli.buffers.previous(event.cli) + search_buffer = event.cli.buffers[SEARCH_BUFFER] + + # Update search state. + if search_buffer.text: + get_search_state(event.cli).text = search_buffer.text + + # Apply search. + input_buffer.apply_search(get_search_state(event.cli), include_current_position=True) + + # Add query to history of search line. + search_buffer.append_to_history() + search_buffer.reset() + + # Focus previous document again. + event.cli.pop_focus() + + @handle(Keys.ControlR, filter= ~has_focus) + def _(event): + get_search_state(event.cli).direction = IncrementalSearchDirection.BACKWARD + event.cli.push_focus(SEARCH_BUFFER) + + @handle(Keys.ControlS, filter= ~has_focus) + def _(event): + get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD + event.cli.push_focus(SEARCH_BUFFER) + + def incremental_search(cli, direction, count=1): + " Apply search, but keep search buffer focussed. " + # Update search_state. + search_state = get_search_state(cli) + direction_changed = search_state.direction != direction + + search_state.text = cli.buffers[SEARCH_BUFFER].text + search_state.direction = direction + + # Apply search to current buffer. + if not direction_changed: + input_buffer = cli.buffers.previous(cli) + input_buffer.apply_search(search_state, + include_current_position=False, count=count) + + @handle(Keys.ControlR, filter=has_focus) + @handle(Keys.Up, filter=has_focus) + def _(event): + incremental_search(event.cli, IncrementalSearchDirection.BACKWARD, count=event.arg) + + @handle(Keys.ControlS, filter=has_focus) + @handle(Keys.Down, filter=has_focus) + def _(event): + incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg) + + return registry + + +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. + """ + 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/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/named_commands.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/named_commands.py new file mode 100644 index 00000000000..f80c439fc6d --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/named_commands.py @@ -0,0 +1,578 @@ +""" +Key bindings which are also known by GNU readline by the given names. + +See: http://www.delorie.com/gnu/docs/readline/rlman_13.html +""" +from __future__ import unicode_literals +from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER +from prompt_toolkit.selection import PasteMode +from six.moves import range +import six + +from .completion import generate_completions, display_completions_like_readline +from prompt_toolkit.document import Document +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.key_binding.input_processor import KeyPress +from prompt_toolkit.keys import Keys + +__all__ = ( + 'get_by_name', +) + + +# Registry that maps the Readline command names to their handlers. +_readline_commands = {} + +def register(name): + """ + Store handler in the `_readline_commands` dictionary. + """ + assert isinstance(name, six.text_type) + def decorator(handler): + assert callable(handler) + + _readline_commands[name] = handler + return handler + return decorator + + +def get_by_name(name): + """ + Return the handler for the (Readline) command with the given name. + """ + try: + return _readline_commands[name] + except KeyError: + raise KeyError('Unknown readline command: %r' % name) + +# +# Commands for moving +# See: http://www.delorie.com/gnu/docs/readline/rlman_14.html +# + +@register('beginning-of-line') +def beginning_of_line(event): + " Move to the start of the current line. " + buff = event.current_buffer + buff.cursor_position += buff.document.get_start_of_line_position(after_whitespace=False) + + +@register('end-of-line') +def end_of_line(event): + " Move to the end of the line. " + buff = event.current_buffer + buff.cursor_position += buff.document.get_end_of_line_position() + + +@register('forward-char') +def forward_char(event): + " Move forward a character. " + buff = event.current_buffer + buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg) + + +@register('backward-char') +def backward_char(event): + " Move back a character. " + buff = event.current_buffer + buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg) + + +@register('forward-word') +def forward_word(event): + """ + Move forward to the end of the next word. Words are composed of letters and + digits. + """ + buff = event.current_buffer + pos = buff.document.find_next_word_ending(count=event.arg) + + if pos: + buff.cursor_position += pos + + +@register('backward-word') +def backward_word(event): + """ + Move back to the start of the current or previous word. Words are composed + of letters and digits. + """ + buff = event.current_buffer + pos = buff.document.find_previous_word_beginning(count=event.arg) + + if pos: + buff.cursor_position += pos + + +@register('clear-screen') +def clear_screen(event): + """ + Clear the screen and redraw everything at the top of the screen. + """ + event.cli.renderer.clear() + + +@register('redraw-current-line') +def redraw_current_line(event): + """ + Refresh the current line. + (Readline defines this command, but prompt-toolkit doesn't have it.) + """ + pass + +# +# Commands for manipulating the history. +# See: http://www.delorie.com/gnu/docs/readline/rlman_15.html +# + +@register('accept-line') +def accept_line(event): + " Accept the line regardless of where the cursor is. " + b = event.current_buffer + b.accept_action.validate_and_handle(event.cli, b) + + +@register('previous-history') +def previous_history(event): + " Move `back` through the history list, fetching the previous command. " + event.current_buffer.history_backward(count=event.arg) + + +@register('next-history') +def next_history(event): + " Move `forward` through the history list, fetching the next command. " + event.current_buffer.history_forward(count=event.arg) + + +@register('beginning-of-history') +def beginning_of_history(event): + " Move to the first line in the history. " + event.current_buffer.go_to_history(0) + + +@register('end-of-history') +def end_of_history(event): + """ + Move to the end of the input history, i.e., the line currently being entered. + """ + event.current_buffer.history_forward(count=10**100) + buff = event.current_buffer + buff.go_to_history(len(buff._working_lines) - 1) + + +@register('reverse-search-history') +def reverse_search_history(event): + """ + Search backward starting at the current line and moving `up` through + the history as necessary. This is an incremental search. + """ + event.cli.current_search_state.direction = IncrementalSearchDirection.BACKWARD + event.cli.push_focus(SEARCH_BUFFER) + + +# +# Commands for changing text +# + +@register('end-of-file') +def end_of_file(event): + """ + Exit. + """ + event.cli.exit() + + +@register('delete-char') +def delete_char(event): + " Delete character before the cursor. " + deleted = event.current_buffer.delete(count=event.arg) + if not deleted: + event.cli.output.bell() + + +@register('backward-delete-char') +def backward_delete_char(event): + " Delete the character behind the cursor. " + if event.arg < 0: + # When a negative argument has been given, this should delete in front + # of the cursor. + deleted = event.current_buffer.delete(count=-event.arg) + else: + deleted = event.current_buffer.delete_before_cursor(count=event.arg) + + if not deleted: + event.cli.output.bell() + + +@register('self-insert') +def self_insert(event): + " Insert yourself. " + event.current_buffer.insert_text(event.data * event.arg) + + +@register('transpose-chars') +def transpose_chars(event): + """ + Emulate Emacs transpose-char behavior: at the beginning of the buffer, + do nothing. At the end of a line or buffer, swap the characters before + the cursor. Otherwise, move the cursor right, and then swap the + characters before the cursor. + """ + b = event.current_buffer + p = b.cursor_position + if p == 0: + return + elif p == len(b.text) or b.text[p] == '\n': + b.swap_characters_before_cursor() + else: + b.cursor_position += b.document.get_cursor_right_position() + b.swap_characters_before_cursor() + + +@register('uppercase-word') +def uppercase_word(event): + """ + Uppercase the current (or following) word. + """ + buff = event.current_buffer + + for i in range(event.arg): + pos = buff.document.find_next_word_ending() + words = buff.document.text_after_cursor[:pos] + buff.insert_text(words.upper(), overwrite=True) + + +@register('downcase-word') +def downcase_word(event): + """ + Lowercase the current (or following) word. + """ + buff = event.current_buffer + + for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!! + pos = buff.document.find_next_word_ending() + words = buff.document.text_after_cursor[:pos] + buff.insert_text(words.lower(), overwrite=True) + + +@register('capitalize-word') +def capitalize_word(event): + """ + Capitalize the current (or following) word. + """ + buff = event.current_buffer + + for i in range(event.arg): + pos = buff.document.find_next_word_ending() + words = buff.document.text_after_cursor[:pos] + buff.insert_text(words.title(), overwrite=True) + + +@register('quoted-insert') +def quoted_insert(event): + """ + Add the next character typed to the line verbatim. This is how to insert + key sequences like C-q, for example. + """ + event.cli.quoted_insert = True + + +# +# Killing and yanking. +# + +@register('kill-line') +def kill_line(event): + """ + Kill the text from the cursor to the end of the line. + + If we are at the end of the line, this should remove the newline. + (That way, it is possible to delete multiple lines by executing this + command multiple times.) + """ + buff = event.current_buffer + if event.arg < 0: + deleted = buff.delete_before_cursor(count=-buff.document.get_start_of_line_position()) + else: + if buff.document.current_char == '\n': + deleted = buff.delete(1) + else: + deleted = buff.delete(count=buff.document.get_end_of_line_position()) + event.cli.clipboard.set_text(deleted) + + +@register('kill-word') +def kill_word(event): + """ + Kill from point to the end of the current word, or if between words, to the + end of the next word. Word boundaries are the same as forward-word. + """ + buff = event.current_buffer + pos = buff.document.find_next_word_ending(count=event.arg) + + if pos: + deleted = buff.delete(count=pos) + event.cli.clipboard.set_text(deleted) + + +@register('unix-word-rubout') +def unix_word_rubout(event, WORD=True): + """ + Kill the word behind point, using whitespace as a word boundary. + Usually bound to ControlW. + """ + buff = event.current_buffer + pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD) + + if pos is None: + # Nothing found? delete until the start of the document. (The + # input starts with whitespace and no words were found before the + # cursor.) + pos = - buff.cursor_position + + if pos: + deleted = buff.delete_before_cursor(count=-pos) + + # If the previous key press was also Control-W, concatenate deleted + # text. + if event.is_repeat: + deleted += event.cli.clipboard.get_data().text + + event.cli.clipboard.set_text(deleted) + else: + # Nothing to delete. Bell. + event.cli.output.bell() + + +@register('backward-kill-word') +def backward_kill_word(event): + """ + Kills the word before point, using "not a letter nor a digit" as a word boundary. + Usually bound to M-Del or M-Backspace. + """ + unix_word_rubout(event, WORD=False) + + +@register('delete-horizontal-space') +def delete_horizontal_space(event): + " Delete all spaces and tabs around point. " + buff = event.current_buffer + text_before_cursor = buff.document.text_before_cursor + text_after_cursor = buff.document.text_after_cursor + + delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip('\t ')) + delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip('\t ')) + + buff.delete_before_cursor(count=delete_before) + buff.delete(count=delete_after) + + +@register('unix-line-discard') +def unix_line_discard(event): + """ + Kill backward from the cursor to the beginning of the current line. + """ + buff = event.current_buffer + + if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0: + buff.delete_before_cursor(count=1) + else: + deleted = buff.delete_before_cursor(count=-buff.document.get_start_of_line_position()) + event.cli.clipboard.set_text(deleted) + + +@register('yank') +def yank(event): + """ + Paste before cursor. + """ + event.current_buffer.paste_clipboard_data( + event.cli.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS) + +@register('yank-nth-arg') +def yank_nth_arg(event): + """ + Insert the first argument of the previous command. With an argument, insert + the nth word from the previous command (start counting at 0). + """ + n = (event.arg if event.arg_present else None) + event.current_buffer.yank_nth_arg(n) + + +@register('yank-last-arg') +def yank_last_arg(event): + """ + Like `yank_nth_arg`, but if no argument has been given, yank the last word + of each line. + """ + n = (event.arg if event.arg_present else None) + event.current_buffer.yank_last_arg(n) + +@register('yank-pop') +def yank_pop(event): + """ + Rotate the kill ring, and yank the new top. Only works following yank or + yank-pop. + """ + buff = event.current_buffer + doc_before_paste = buff.document_before_paste + clipboard = event.cli.clipboard + + if doc_before_paste is not None: + buff.document = doc_before_paste + clipboard.rotate() + buff.paste_clipboard_data( + clipboard.get_data(), paste_mode=PasteMode.EMACS) + +# +# Completion. +# + +@register('complete') +def complete(event): + " Attempt to perform completion. " + display_completions_like_readline(event) + + +@register('menu-complete') +def menu_complete(event): + """ + Generate completions, or go to the next completion. (This is the default + way of completing input in prompt_toolkit.) + """ + generate_completions(event) + + +@register('menu-complete-backward') +def menu_complete_backward(event): + " Move backward through the list of possible completions. " + event.current_buffer.complete_previous() + +# +# Keyboard macros. +# + +@register('start-kbd-macro') +def start_kbd_macro(event): + """ + Begin saving the characters typed into the current keyboard macro. + """ + event.cli.input_processor.start_macro() + + +@register('end-kbd-macro') +def start_kbd_macro(event): + """ + Stop saving the characters typed into the current keyboard macro and save + the definition. + """ + event.cli.input_processor.end_macro() + + +@register('call-last-kbd-macro') +def start_kbd_macro(event): + """ + Re-execute the last keyboard macro defined, by making the characters in the + macro appear as if typed at the keyboard. + """ + event.cli.input_processor.call_macro() + + +@register('print-last-kbd-macro') +def print_last_kbd_macro(event): + " Print the last keboard macro. " + # TODO: Make the format suitable for the inputrc file. + def print_macro(): + for k in event.cli.input_processor.macro: + print(k) + event.cli.run_in_terminal(print_macro) + +# +# Miscellaneous Commands. +# + +@register('undo') +def undo(event): + " Incremental undo. " + event.current_buffer.undo() + + +@register('insert-comment') +def insert_comment(event): + """ + Without numeric argument, comment all lines. + With numeric argument, uncomment all lines. + In any case accept the input. + """ + buff = event.current_buffer + + # Transform all lines. + if event.arg != 1: + def change(line): + return line[1:] if line.startswith('#') else line + else: + def change(line): + return '#' + line + + buff.document = Document( + text='\n'.join(map(change, buff.text.splitlines())), + cursor_position=0) + + # Accept input. + buff.accept_action.validate_and_handle(event.cli, buff) + + +@register('vi-editing-mode') +def vi_editing_mode(event): + " Switch to Vi editing mode. " + event.cli.editing_mode = EditingMode.VI + + +@register('emacs-editing-mode') +def emacs_editing_mode(event): + " Switch to Emacs editing mode. " + event.cli.editing_mode = EditingMode.EMACS + + +@register('prefix-meta') +def prefix_meta(event): + """ + Metafy the next character typed. This is for keyboards without a meta key. + + Sometimes people also want to bind other keys to Meta, e.g. 'jj':: + + registry.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta) + """ + event.cli.input_processor.feed(KeyPress(Keys.Escape)) + + +@register('operate-and-get-next') +def operate_and_get_next(event): + """ + Accept the current line for execution and fetch the next line relative to + the current line from the history for editing. + """ + buff = event.current_buffer + new_index = buff.working_index + 1 + + # Accept the current input. (This will also redraw the interface in the + # 'done' state.) + buff.accept_action.validate_and_handle(event.cli, buff) + + # Set the new index at the start of the next run. + def set_working_index(): + if new_index < len(buff._working_lines): + buff.working_index = new_index + + event.cli.pre_run_callables.append(set_working_index) + + +@register('edit-and-execute-command') +def edit_and_execute(event): + """ + Invoke an editor on the current command line, and accept the result. + """ + buff = event.current_buffer + + buff.open_in_editor(event.cli) + buff.accept_action.validate_and_handle(event.cli, buff) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/scroll.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/scroll.py new file mode 100644 index 00000000000..2cc58129ffc --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/scroll.py @@ -0,0 +1,185 @@ +""" +Key bindings, for scrolling up and down through pages. + +This are separate bindings, because GNU readline doesn't have them, but +they are very useful for navigating through long multiline buffers, like in +Vi, Emacs, etc... +""" +from __future__ import unicode_literals + +from prompt_toolkit.layout.utils import find_window_for_buffer_name +from six.moves import range + +__all__ = ( + 'scroll_forward', + 'scroll_backward', + 'scroll_half_page_up', + 'scroll_half_page_down', + 'scroll_one_line_up', + 'scroll_one_line_down', +) + + +def _current_window_for_event(event): + """ + Return the `Window` for the currently focussed Buffer. + """ + return find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + + +def scroll_forward(event, half=False): + """ + Scroll window down. + """ + w = _current_window_for_event(event) + b = event.cli.current_buffer + + if w and w.render_info: + info = w.render_info + ui_content = info.ui_content + + # Height to scroll. + scroll_height = info.window_height + if half: + scroll_height //= 2 + + # Calculate how many lines is equivalent to that vertical space. + y = b.document.cursor_position_row + 1 + height = 0 + while y < ui_content.line_count: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y += 1 + else: + break + + b.cursor_position = b.document.translate_row_col_to_index(y, 0) + + +def scroll_backward(event, half=False): + """ + Scroll window up. + """ + w = _current_window_for_event(event) + b = event.cli.current_buffer + + if w and w.render_info: + info = w.render_info + + # Height to scroll. + scroll_height = info.window_height + if half: + scroll_height //= 2 + + # Calculate how many lines is equivalent to that vertical space. + y = max(0, b.document.cursor_position_row - 1) + height = 0 + while y > 0: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y -= 1 + else: + break + + b.cursor_position = b.document.translate_row_col_to_index(y, 0) + + +def scroll_half_page_down(event): + """ + Same as ControlF, but only scroll half a page. + """ + scroll_forward(event, half=True) + + +def scroll_half_page_up(event): + """ + Same as ControlB, but only scroll half a page. + """ + scroll_backward(event, half=True) + + +def scroll_one_line_down(event): + """ + scroll_offset += 1 + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + b = event.cli.current_buffer + + if w: + # When the cursor is at the top, move to the next line. (Otherwise, only scroll.) + if w.render_info: + info = w.render_info + + if w.vertical_scroll < info.content_height - info.window_height: + if info.cursor_position.y <= info.configured_scroll_offsets.top: + b.cursor_position += b.document.get_cursor_down_position() + + w.vertical_scroll += 1 + + +def scroll_one_line_up(event): + """ + scroll_offset -= 1 + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + b = event.cli.current_buffer + + if w: + # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.) + if w.render_info: + info = w.render_info + + if w.vertical_scroll > 0: + first_line_height = info.get_height_for_line(info.first_visible_line()) + + cursor_up = info.cursor_position.y - (info.window_height - 1 - first_line_height - + info.configured_scroll_offsets.bottom) + + # Move cursor up, as many steps as the height of the first line. + # TODO: not entirely correct yet, in case of line wrapping and many long lines. + for _ in range(max(0, cursor_up)): + b.cursor_position += b.document.get_cursor_up_position() + + # Scroll window + w.vertical_scroll -= 1 + + +def scroll_page_down(event): + """ + Scroll page down. (Prefer the cursor at the top of the page, after scrolling.) + """ + w = _current_window_for_event(event) + b = event.cli.current_buffer + + if w and w.render_info: + # Scroll down one page. + line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1) + w.vertical_scroll = line_index + + b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) + b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) + + +def scroll_page_up(event): + """ + Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.) + """ + w = _current_window_for_event(event) + b = event.cli.current_buffer + + if w and w.render_info: + # Put cursor at the first visible line. (But make sure that the cursor + # moves at least one line up.) + line_index = max(0, min(w.render_info.first_visible_line(), + b.document.cursor_position_row - 1)) + + b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) + b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) + + # Set the scroll offset. We can safely set it to zero; the Window will + # make sure that it scrolls at least until the cursor becomes visible. + w.vertical_scroll = 0 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/utils.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/utils.py new file mode 100644 index 00000000000..caf08c5c1bd --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/utils.py @@ -0,0 +1,25 @@ +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/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/vi.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/vi.py new file mode 100644 index 00000000000..72568ee273d --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/vi.py @@ -0,0 +1,1903 @@ +# pylint: disable=function-redefined +from __future__ import unicode_literals +from prompt_toolkit.buffer import ClipboardData, indent, unindent, reshape_text +from prompt_toolkit.document import Document +from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER +from prompt_toolkit.filters import Filter, Condition, HasArg, Always, IsReadOnly +from prompt_toolkit.filters.cli import ViNavigationMode, ViInsertMode, ViInsertMultipleMode, ViReplaceMode, ViSelectionMode, ViWaitingForTextObjectMode, ViDigraphMode, ViMode +from prompt_toolkit.key_binding.digraphs import DIGRAPHS +from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode +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 .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, BaseRegistry + +import prompt_toolkit.filters as filters +from six.moves import range +import codecs +import six +import string + +try: + from itertools import accumulate +except ImportError: # < Python 3.2 + def accumulate(iterable): + " Super simpel 'accumulate' implementation. " + total = 0 + for item in iterable: + total += item + yield total + +__all__ = ( + 'load_vi_bindings', + 'load_vi_search_bindings', + 'load_vi_system_bindings', + 'load_extra_vi_page_navigation_bindings', +) + +if six.PY2: + ascii_lowercase = string.ascii_lowercase.decode('ascii') +else: + ascii_lowercase = string.ascii_lowercase + +vi_register_names = ascii_lowercase + '0123456789' + + +class TextObjectType(object): + EXCLUSIVE = 'EXCLUSIVE' + INCLUSIVE = 'INCLUSIVE' + LINEWISE = 'LINEWISE' + BLOCK = 'BLOCK' + + +class TextObject(object): + """ + Return struct for functions wrapped in ``text_object``. + Both `start` and `end` are relative to the current cursor position. + """ + def __init__(self, start, end=0, type=TextObjectType.EXCLUSIVE): + self.start = start + self.end = end + self.type = type + + @property + def selection_type(self): + if self.type == TextObjectType.LINEWISE: + return SelectionType.LINES + if self.type == TextObjectType.BLOCK: + return SelectionType.BLOCK + else: + return SelectionType.CHARACTERS + + def sorted(self): + """ + Return a (start, end) tuple where start <= end. + """ + if self.start < self.end: + return self.start, self.end + else: + return self.end, self.start + + def operator_range(self, document): + """ + Return a (start, end) tuple with start <= end that indicates the range + operators should operate on. + `buffer` is used to get start and end of line positions. + """ + start, end = self.sorted() + doc = document + + if (self.type == TextObjectType.EXCLUSIVE and + doc.translate_index_to_position(end + doc.cursor_position)[1] == 0): + # If the motion is exclusive and the end of motion is on the first + # column, the end position becomes end of previous line. + end -= 1 + if self.type == TextObjectType.INCLUSIVE: + end += 1 + if self.type == TextObjectType.LINEWISE: + # Select whole lines + row, col = doc.translate_index_to_position(start + doc.cursor_position) + start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position + row, col = doc.translate_index_to_position(end + doc.cursor_position) + end = doc.translate_row_col_to_index(row, len(doc.lines[row])) - doc.cursor_position + return start, end + + def get_line_numbers(self, buffer): + """ + Return a (start_line, end_line) pair. + """ + # Get absolute cursor positions from the text object. + from_, to = self.operator_range(buffer.document) + from_ += buffer.cursor_position + to += buffer.cursor_position + + # Take the start of the lines. + from_, _ = buffer.document.translate_index_to_position(from_) + to, _ = buffer.document.translate_index_to_position(to) + + return from_, to + + def cut(self, buffer): + """ + Turn text object into `ClipboardData` instance. + """ + from_, to = self.operator_range(buffer.document) + + from_ += buffer.cursor_position + to += buffer.cursor_position + to -= 1 # SelectionState does not include the end position, `operator_range` does. + + document = Document(buffer.text, to, SelectionState( + original_cursor_position=from_, type=self.selection_type)) + + new_document, clipboard_data = document.cut_selection() + return new_document, clipboard_data + + +def create_text_object_decorator(registry): + """ + Create a decorator that can be used to register Vi text object implementations. + """ + assert isinstance(registry, BaseRegistry) + + operator_given = ViWaitingForTextObjectMode() + navigation_mode = ViNavigationMode() + selection_mode = ViSelectionMode() + + def text_object_decorator(*keys, **kw): + """ + Register a text object function. + + Usage:: + + @text_object('w', filter=..., no_move_handler=False) + def handler(event): + # Return a text object for this key. + return TextObject(...) + + :param no_move_handler: Disable the move handler in navigation mode. + (It's still active in selection mode.) + """ + filter = kw.pop('filter', Always()) + no_move_handler = kw.pop('no_move_handler', False) + no_selection_handler = kw.pop('no_selection_handler', False) + eager = kw.pop('eager', False) + assert not kw + + def decorator(text_object_func): + assert callable(text_object_func) + + @registry.add_binding(*keys, filter=operator_given & filter, eager=eager) + def _(event): + # Arguments are multiplied. + vi_state = event.cli.vi_state + event._arg = (vi_state.operator_arg or 1) * (event.arg or 1) + + # Call the text object handler. + text_obj = text_object_func(event) + if text_obj is not None: + assert isinstance(text_obj, TextObject) + + # Call the operator function with the text object. + vi_state.operator_func(event, text_obj) + + # Clear operator. + event.cli.vi_state.operator_func = None + event.cli.vi_state.operator_arg = None + + # Register a move operation. (Doesn't need an operator.) + if not no_move_handler: + @registry.add_binding(*keys, filter=~operator_given & filter & navigation_mode, eager=eager) + def _(event): + " Move handler for navigation mode. " + text_object = text_object_func(event) + event.current_buffer.cursor_position += text_object.start + + # Register a move selection operation. + if not no_selection_handler: + @registry.add_binding(*keys, filter=~operator_given & filter & selection_mode, eager=eager) + def _(event): + " Move handler for selection mode. " + text_object = text_object_func(event) + buff = event.current_buffer + + # When the text object has both a start and end position, like 'i(' or 'iw', + # Turn this into a selection, otherwise the cursor. + if text_object.end: + # Take selection positions from text object. + start, end = text_object.operator_range(buff.document) + start += buff.cursor_position + end += buff.cursor_position + + buff.selection_state.original_cursor_position = start + buff.cursor_position = end + + # Take selection type from text object. + if text_object.type == TextObjectType.LINEWISE: + buff.selection_state.type = SelectionType.LINES + else: + buff.selection_state.type = SelectionType.CHARACTERS + else: + event.current_buffer.cursor_position += text_object.start + + # Make it possible to chain @text_object decorators. + return text_object_func + + return decorator + return text_object_decorator + + +def create_operator_decorator(registry): + """ + Create a decorator that can be used for registering Vi operators. + """ + assert isinstance(registry, BaseRegistry) + + operator_given = ViWaitingForTextObjectMode() + navigation_mode = ViNavigationMode() + selection_mode = ViSelectionMode() + + def operator_decorator(*keys, **kw): + """ + Register a Vi operator. + + Usage:: + + @operator('d', filter=...) + def handler(cli, text_object): + # Do something with the text object here. + """ + filter = kw.pop('filter', Always()) + eager = kw.pop('eager', False) + assert not kw + + def decorator(operator_func): + @registry.add_binding(*keys, filter=~operator_given & filter & navigation_mode, eager=eager) + def _(event): + """ + Handle operator in navigation mode. + """ + # When this key binding is matched, only set the operator + # function in the ViState. We should execute it after a text + # object has been received. + event.cli.vi_state.operator_func = operator_func + event.cli.vi_state.operator_arg = event.arg + + @registry.add_binding(*keys, filter=~operator_given & filter & selection_mode, eager=eager) + def _(event): + """ + Handle operator in selection mode. + """ + buff = event.current_buffer + selection_state = buff.selection_state + + # Create text object from selection. + if selection_state.type == SelectionType.LINES: + text_obj_type = TextObjectType.LINEWISE + elif selection_state.type == SelectionType.BLOCK: + text_obj_type = TextObjectType.BLOCK + else: + text_obj_type = TextObjectType.INCLUSIVE + + text_object = TextObject( + selection_state.original_cursor_position - buff.cursor_position, + type=text_obj_type) + + # Execute operator. + operator_func(event, text_object) + + # Quit selection mode. + buff.selection_state = None + + return operator_func + return decorator + return operator_decorator + + +def load_vi_bindings(get_search_state=None): + """ + Vi extensions. + + # Overview of Readline Vi commands: + # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf + + :param get_search_state: None or a callable that takes a + CommandLineInterface and returns a SearchState. + """ + # Note: Some key bindings have the "~IsReadOnly()" filter added. This + # prevents the handler to be executed when the focus is on a + # read-only buffer. + # This is however only required for those that change the ViState to + # INSERT mode. The `Buffer` class itself throws the + # `EditReadOnlyBuffer` exception for any text operations which is + # 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 + + # Default get_search_state. + if get_search_state is None: + def get_search_state(cli): return cli.search_state + + # (Note: Always take the navigation bindings in read-only mode, even when + # ViState says different.) + navigation_mode = ViNavigationMode() + insert_mode = ViInsertMode() + insert_multiple_mode = ViInsertMultipleMode() + replace_mode = ViReplaceMode() + selection_mode = ViSelectionMode() + operator_given = ViWaitingForTextObjectMode() + digraph_mode = ViDigraphMode() + + vi_transform_functions = [ + # Rot 13 transformation + (('g', '?'), Always(), lambda string: codecs.encode(string, 'rot_13')), + + # To lowercase + (('g', 'u'), Always(), lambda string: string.lower()), + + # To uppercase. + (('g', 'U'), Always(), lambda string: string.upper()), + + # Swap case. + (('g', '~'), Always(), lambda string: string.swapcase()), + (('~', ), Condition(lambda cli: cli.vi_state.tilde_operator), lambda string: string.swapcase()), + ] + + # Insert a character literally (quoted insert). + handle(Keys.ControlV, filter=insert_mode)(get_by_name('quoted-insert')) + + @handle(Keys.Escape) + def _(event): + """ + Escape goes to vi navigation mode. + """ + buffer = event.current_buffer + vi_state = event.cli.vi_state + + if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): + buffer.cursor_position += buffer.document.get_cursor_left_position() + + vi_state.reset(InputMode.NAVIGATION) + + if bool(buffer.selection_state): + buffer.exit_selection() + + @handle('k', filter=selection_mode) + def _(event): + """ + Arrow up in selection mode. + """ + event.current_buffer.cursor_up(count=event.arg) + + @handle('j', filter=selection_mode) + def _(event): + """ + Arrow down in selection mode. + """ + event.current_buffer.cursor_down(count=event.arg) + + @handle(Keys.Up, filter=navigation_mode) + @handle(Keys.ControlP, filter=navigation_mode) + def _(event): + """ + Arrow up and ControlP in navigation mode go up. + """ + event.current_buffer.auto_up(count=event.arg) + + @handle('k', filter=navigation_mode) + def _(event): + """ + Go up, but if we enter a new history entry, move to the start of the + line. + """ + event.current_buffer.auto_up( + count=event.arg, go_to_start_of_line_if_history_changes=True) + + @handle(Keys.Down, filter=navigation_mode) + @handle(Keys.ControlN, filter=navigation_mode) + def _(event): + """ + Arrow down and Control-N in navigation mode. + """ + event.current_buffer.auto_down(count=event.arg) + + @handle('j', filter=navigation_mode) + def _(event): + """ + Go down, but if we enter a new history entry, go to the start of the line. + """ + event.current_buffer.auto_down( + count=event.arg, go_to_start_of_line_if_history_changes=True) + + @handle(Keys.ControlH, filter=navigation_mode) + @handle(Keys.Backspace, filter=navigation_mode) + def _(event): + """ + In navigation-mode, move cursor. + """ + event.current_buffer.cursor_position += \ + event.current_buffer.document.get_cursor_left_position(count=event.arg) + + @handle(Keys.ControlN, filter=insert_mode) + def _(event): + b = event.current_buffer + + if b.complete_state: + b.complete_next() + else: + event.cli.start_completion(select_first=True) + + @handle(Keys.ControlP, filter=insert_mode) + def _(event): + """ + Control-P: To previous completion. + """ + b = event.current_buffer + + if b.complete_state: + b.complete_previous() + else: + event.cli.start_completion(select_last=True) + + @handle(Keys.ControlY, filter=insert_mode) + def _(event): + """ + Accept current completion. + """ + event.current_buffer.complete_state = None + + @handle(Keys.ControlE, filter=insert_mode) + def _(event): + """ + Cancel completion. Go back to originally typed text. + """ + event.current_buffer.cancel_completion() + + @handle(Keys.ControlJ, filter=navigation_mode) # XXX: only if the selected buffer has a return handler. + def _(event): + """ + In navigation mode, pressing enter will always return the input. + """ + b = event.current_buffer + + if b.accept_action.is_returnable: + b.accept_action.validate_and_handle(event.cli, b) + + # ** In navigation mode ** + + # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html + + @handle(Keys.Insert, filter=navigation_mode) + def _(event): + " Presing the Insert key. " + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('a', filter=navigation_mode & ~IsReadOnly()) + # ~IsReadOnly, because we want to stay in navigation mode for + # read-only buffers. + def _(event): + event.current_buffer.cursor_position += event.current_buffer.document.get_cursor_right_position() + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('A', filter=navigation_mode & ~IsReadOnly()) + def _(event): + event.current_buffer.cursor_position += event.current_buffer.document.get_end_of_line_position() + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('C', filter=navigation_mode & ~IsReadOnly()) + def _(event): + """ + # Change to end of line. + # Same as 'c$' (which is implemented elsewhere.) + """ + buffer = event.current_buffer + + deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) + event.cli.clipboard.set_text(deleted) + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('c', 'c', filter=navigation_mode & ~IsReadOnly()) + @handle('S', filter=navigation_mode & ~IsReadOnly()) + def _(event): # TODO: implement 'arg' + """ + Change current line + """ + buffer = event.current_buffer + + # We copy the whole line. + data = ClipboardData(buffer.document.current_line, SelectionType.LINES) + event.cli.clipboard.set_data(data) + + # But we delete after the whitespace + buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) + buffer.delete(count=buffer.document.get_end_of_line_position()) + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('D', filter=navigation_mode) + def _(event): + buffer = event.current_buffer + deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) + event.cli.clipboard.set_text(deleted) + + @handle('d', 'd', filter=navigation_mode) + def _(event): + """ + Delete line. (Or the following 'n' lines.) + """ + buffer = event.current_buffer + + # Split string in before/deleted/after text. + lines = buffer.document.lines + + before = '\n'.join(lines[:buffer.document.cursor_position_row]) + deleted = '\n'.join(lines[buffer.document.cursor_position_row: + buffer.document.cursor_position_row + event.arg]) + after = '\n'.join(lines[buffer.document.cursor_position_row + event.arg:]) + + # Set new text. + if before and after: + before = before + '\n' + + # Set text and cursor position. + buffer.document = Document( + text=before + after, + # Cursor At the start of the first 'after' line, after the leading whitespace. + cursor_position = len(before) + len(after) - len(after.lstrip(' '))) + + # Set clipboard data + event.cli.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES)) + + @handle('x', filter=selection_mode) + def _(event): + """ + Cut selection. + ('x' is not an operator.) + """ + clipboard_data = event.current_buffer.cut_selection() + event.cli.clipboard.set_data(clipboard_data) + + @handle('i', filter=navigation_mode & ~IsReadOnly()) + def _(event): + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('I', filter=navigation_mode & ~IsReadOnly()) + def _(event): + event.cli.vi_state.input_mode = InputMode.INSERT + event.current_buffer.cursor_position += \ + event.current_buffer.document.get_start_of_line_position(after_whitespace=True) + + @Condition + def in_block_selection(cli): + buff = cli.current_buffer + return buff.selection_state and buff.selection_state.type == SelectionType.BLOCK + + @handle('I', filter=in_block_selection & ~IsReadOnly()) + def go_to_block_selection(event, after=False): + " Insert in block selection mode. " + buff = event.current_buffer + + # Store all cursor positions. + positions = [] + + if after: + def get_pos(from_to): + return from_to[1] + 1 + else: + def get_pos(from_to): + return from_to[0] + + for i, from_to in enumerate(buff.document.selection_ranges()): + positions.append(get_pos(from_to)) + if i == 0: + buff.cursor_position = get_pos(from_to) + + buff.multiple_cursor_positions = positions + + # Go to 'INSERT_MULTIPLE' mode. + event.cli.vi_state.input_mode = InputMode.INSERT_MULTIPLE + buff.exit_selection() + + @handle('A', filter=in_block_selection & ~IsReadOnly()) + def _(event): + go_to_block_selection(event, after=True) + + @handle('J', filter=navigation_mode & ~IsReadOnly()) + def _(event): + " Join lines. " + for i in range(event.arg): + event.current_buffer.join_next_line() + + @handle('g', 'J', filter=navigation_mode & ~IsReadOnly()) + def _(event): + " Join lines without space. " + for i in range(event.arg): + event.current_buffer.join_next_line(separator='') + + @handle('J', filter=selection_mode & ~IsReadOnly()) + def _(event): + " Join selected lines. " + event.current_buffer.join_selected_lines() + + @handle('g', 'J', filter=selection_mode & ~IsReadOnly()) + def _(event): + " Join selected lines without space. " + event.current_buffer.join_selected_lines(separator='') + + @handle('p', filter=navigation_mode) + def _(event): + """ + Paste after + """ + event.current_buffer.paste_clipboard_data( + event.cli.clipboard.get_data(), + count=event.arg, + paste_mode=PasteMode.VI_AFTER) + + @handle('P', filter=navigation_mode) + def _(event): + """ + Paste before + """ + event.current_buffer.paste_clipboard_data( + event.cli.clipboard.get_data(), + count=event.arg, + paste_mode=PasteMode.VI_BEFORE) + + @handle('"', Keys.Any, 'p', filter=navigation_mode) + def _(event): + " Paste from named register. " + c = event.key_sequence[1].data + if c in vi_register_names: + data = event.cli.vi_state.named_registers.get(c) + if data: + event.current_buffer.paste_clipboard_data( + data, count=event.arg, paste_mode=PasteMode.VI_AFTER) + + @handle('"', Keys.Any, 'P', filter=navigation_mode) + def _(event): + " Paste (before) from named register. " + c = event.key_sequence[1].data + if c in vi_register_names: + data = event.cli.vi_state.named_registers.get(c) + if data: + event.current_buffer.paste_clipboard_data( + data, count=event.arg, paste_mode=PasteMode.VI_BEFORE) + + @handle('r', Keys.Any, filter=navigation_mode) + def _(event): + """ + Replace single character under cursor + """ + event.current_buffer.insert_text(event.data * event.arg, overwrite=True) + event.current_buffer.cursor_position -= 1 + + @handle('R', filter=navigation_mode) + def _(event): + """ + Go to 'replace'-mode. + """ + event.cli.vi_state.input_mode = InputMode.REPLACE + + @handle('s', filter=navigation_mode & ~IsReadOnly()) + def _(event): + """ + Substitute with new text + (Delete character(s) and go to insert mode.) + """ + text = event.current_buffer.delete(count=event.arg) + event.cli.clipboard.set_text(text) + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('u', filter=navigation_mode, save_before=(lambda e: False)) + def _(event): + for i in range(event.arg): + event.current_buffer.undo() + + @handle('V', filter=navigation_mode) + def _(event): + """ + Start lines selection. + """ + event.current_buffer.start_selection(selection_type=SelectionType.LINES) + + @handle(Keys.ControlV, filter=navigation_mode) + def _(event): + " Enter block selection mode. " + event.current_buffer.start_selection(selection_type=SelectionType.BLOCK) + + @handle('V', filter=selection_mode) + def _(event): + """ + Exit line selection mode, or go from non line selection mode to line + selection mode. + """ + selection_state = event.current_buffer.selection_state + + if selection_state.type != SelectionType.LINES: + selection_state.type = SelectionType.LINES + else: + event.current_buffer.exit_selection() + + @handle('v', filter=navigation_mode) + def _(event): + " Enter character selection mode. " + event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) + + @handle('v', filter=selection_mode) + def _(event): + """ + Exit character selection mode, or go from non-character-selection mode + to character selection mode. + """ + selection_state = event.current_buffer.selection_state + + if selection_state.type != SelectionType.CHARACTERS: + selection_state.type = SelectionType.CHARACTERS + else: + event.current_buffer.exit_selection() + + @handle(Keys.ControlV, filter=selection_mode) + def _(event): + """ + Exit block selection mode, or go from non block selection mode to block + selection mode. + """ + selection_state = event.current_buffer.selection_state + + if selection_state.type != SelectionType.BLOCK: + selection_state.type = SelectionType.BLOCK + else: + event.current_buffer.exit_selection() + + + @handle('a', 'w', filter=selection_mode) + @handle('a', 'W', filter=selection_mode) + def _(event): + """ + Switch from visual linewise mode to visual characterwise mode. + """ + buffer = event.current_buffer + + if buffer.selection_state and buffer.selection_state.type == SelectionType.LINES: + buffer.selection_state.type = SelectionType.CHARACTERS + + @handle('x', filter=navigation_mode) + def _(event): + """ + Delete character. + """ + text = event.current_buffer.delete(count=event.arg) + event.cli.clipboard.set_text(text) + + @handle('X', filter=navigation_mode) + def _(event): + text = event.current_buffer.delete_before_cursor() + event.cli.clipboard.set_text(text) + + @handle('y', 'y', filter=navigation_mode) + @handle('Y', filter=navigation_mode) + def _(event): + """ + Yank the whole line. + """ + text = '\n'.join(event.current_buffer.document.lines_from_current[:event.arg]) + event.cli.clipboard.set_data(ClipboardData(text, SelectionType.LINES)) + + @handle('+', filter=navigation_mode) + def _(event): + """ + Move to first non whitespace of next line + """ + buffer = event.current_buffer + buffer.cursor_position += buffer.document.get_cursor_down_position(count=event.arg) + buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) + + @handle('-', filter=navigation_mode) + def _(event): + """ + Move to first non whitespace of previous line + """ + buffer = event.current_buffer + buffer.cursor_position += buffer.document.get_cursor_up_position(count=event.arg) + buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) + + @handle('>', '>', filter=navigation_mode) + def _(event): + """ + Indent lines. + """ + buffer = event.current_buffer + current_row = buffer.document.cursor_position_row + indent(buffer, current_row, current_row + event.arg) + + @handle('<', '<', filter=navigation_mode) + def _(event): + """ + Unindent lines. + """ + current_row = event.current_buffer.document.cursor_position_row + unindent(event.current_buffer, current_row, current_row + event.arg) + + @handle('O', filter=navigation_mode & ~IsReadOnly()) + def _(event): + """ + Open line above and enter insertion mode + """ + event.current_buffer.insert_line_above( + copy_margin=not event.cli.in_paste_mode) + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('o', filter=navigation_mode & ~IsReadOnly()) + def _(event): + """ + Open line below and enter insertion mode + """ + event.current_buffer.insert_line_below( + copy_margin=not event.cli.in_paste_mode) + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle('~', filter=navigation_mode) + def _(event): + """ + Reverse case of current character and move cursor forward. + """ + buffer = event.current_buffer + c = buffer.document.current_char + + if c is not None and c != '\n': + buffer.insert_text(c.swapcase(), overwrite=True) + + @handle('g', 'u', 'u', filter=navigation_mode & ~IsReadOnly()) + def _(event): + " Lowercase current line. " + buff = event.current_buffer + buff.transform_current_line(lambda s: s.lower()) + + @handle('g', 'U', 'U', filter=navigation_mode & ~IsReadOnly()) + def _(event): + " Uppercase current line. " + buff = event.current_buffer + buff.transform_current_line(lambda s: s.upper()) + + @handle('g', '~', '~', filter=navigation_mode & ~IsReadOnly()) + def _(event): + " Swap case of the current line. " + buff = event.current_buffer + buff.transform_current_line(lambda s: s.swapcase()) + + @handle('#', filter=navigation_mode) + def _(event): + """ + Go to previous occurence of this word. + """ + b = event.cli.current_buffer + + search_state = get_search_state(event.cli) + search_state.text = b.document.get_word_under_cursor() + search_state.direction = IncrementalSearchDirection.BACKWARD + + b.apply_search(search_state, count=event.arg, + include_current_position=False) + + @handle('*', filter=navigation_mode) + def _(event): + """ + Go to next occurence of this word. + """ + b = event.cli.current_buffer + + search_state = get_search_state(event.cli) + search_state.text = b.document.get_word_under_cursor() + search_state.direction = IncrementalSearchDirection.FORWARD + + b.apply_search(search_state, count=event.arg, + include_current_position=False) + + @handle('(', filter=navigation_mode) + def _(event): + # TODO: go to begin of sentence. + # XXX: should become text_object. + pass + + @handle(')', filter=navigation_mode) + def _(event): + # TODO: go to end of sentence. + # XXX: should become text_object. + pass + + operator = create_operator_decorator(registry) + text_object = create_text_object_decorator(registry) + + @text_object(Keys.Any, filter=operator_given) + def _(event): + """ + Unknown key binding while waiting for a text object. + """ + event.cli.output.bell() + + # + # *** Operators *** + # + + def create_delete_and_change_operators(delete_only, with_register=False): + """ + Delete and change operators. + + :param delete_only: Create an operator that deletes, but doesn't go to insert mode. + :param with_register: Copy the deleted text to this named register instead of the clipboard. + """ + if with_register: + handler_keys = ('"', Keys.Any, 'cd'[delete_only]) + else: + handler_keys = 'cd'[delete_only] + + @operator(*handler_keys, filter=~IsReadOnly()) + def delete_or_change_operator(event, text_object): + clipboard_data = None + buff = event.current_buffer + + if text_object: + new_document, clipboard_data = text_object.cut(buff) + buff.document = new_document + + # Set deleted/changed text to clipboard or named register. + if clipboard_data and clipboard_data.text: + if with_register: + reg_name = event.key_sequence[1].data + if reg_name in vi_register_names: + event.cli.vi_state.named_registers[reg_name] = clipboard_data + else: + event.cli.clipboard.set_data(clipboard_data) + + # Only go back to insert mode in case of 'change'. + if not delete_only: + event.cli.vi_state.input_mode = InputMode.INSERT + + create_delete_and_change_operators(False, False) + create_delete_and_change_operators(False, True) + create_delete_and_change_operators(True, False) + create_delete_and_change_operators(True, True) + + def create_transform_handler(filter, transform_func, *a): + @operator(*a, filter=filter & ~IsReadOnly()) + def _(event, text_object): + """ + Apply transformation (uppercase, lowercase, rot13, swap case). + """ + buff = event.current_buffer + start, end = text_object.operator_range(buff.document) + + if start < end: + # Transform. + buff.transform_region( + buff.cursor_position + start, + buff.cursor_position + end, + transform_func) + + # Move cursor + buff.cursor_position += (text_object.end or text_object.start) + + for k, f, func in vi_transform_functions: + create_transform_handler(f, func, *k) + + @operator('y') + def yank_handler(event, text_object): + """ + Yank operator. (Copy text.) + """ + _, clipboard_data = text_object.cut(event.current_buffer) + if clipboard_data.text: + event.cli.clipboard.set_data(clipboard_data) + + @operator('"', Keys.Any, 'y') + def _(event, text_object): + " Yank selection to named register. " + c = event.key_sequence[1].data + if c in vi_register_names: + _, clipboard_data = text_object.cut(event.current_buffer) + event.cli.vi_state.named_registers[c] = clipboard_data + + @operator('>') + def _(event, text_object): + """ + Indent. + """ + buff = event.current_buffer + from_, to = text_object.get_line_numbers(buff) + indent(buff, from_, to + 1, count=event.arg) + + @operator('<') + def _(event, text_object): + """ + Unindent. + """ + buff = event.current_buffer + from_, to = text_object.get_line_numbers(buff) + unindent(buff, from_, to + 1, count=event.arg) + + @operator('g', 'q') + def _(event, text_object): + """ + Reshape text. + """ + buff = event.current_buffer + from_, to = text_object.get_line_numbers(buff) + reshape_text(buff, from_, to) + + # + # *** Text objects *** + # + + @text_object('b') + def _(event): + """ Move one word or token left. """ + return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg) or 0) + + @text_object('B') + def _(event): + """ Move one non-blank word left """ + return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg, WORD=True) or 0) + + @text_object('$') + def key_dollar(event): + """ 'c$', 'd$' and '$': Delete/change/move until end of line. """ + return TextObject(event.current_buffer.document.get_end_of_line_position()) + + @text_object('w') + def _(event): + """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. """ + return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg) or + event.current_buffer.document.get_end_of_document_position()) + + @text_object('W') + def _(event): + """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. """ + return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg, WORD=True) or + event.current_buffer.document.get_end_of_document_position()) + + @text_object('e') + def _(event): + """ End of 'word': 'ce', 'de', 'e' """ + end = event.current_buffer.document.find_next_word_ending(count=event.arg) + return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) + + @text_object('E') + def _(event): + """ End of 'WORD': 'cE', 'dE', 'E' """ + end = event.current_buffer.document.find_next_word_ending(count=event.arg, WORD=True) + return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) + + @text_object('i', 'w', no_move_handler=True) + def _(event): + """ Inner 'word': ciw and diw """ + start, end = event.current_buffer.document.find_boundaries_of_current_word() + return TextObject(start, end) + + @text_object('a', 'w', no_move_handler=True) + def _(event): + """ A 'word': caw and daw """ + start, end = event.current_buffer.document.find_boundaries_of_current_word(include_trailing_whitespace=True) + return TextObject(start, end) + + @text_object('i', 'W', no_move_handler=True) + def _(event): + """ Inner 'WORD': ciW and diW """ + start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True) + return TextObject(start, end) + + @text_object('a', 'W', no_move_handler=True) + def _(event): + """ A 'WORD': caw and daw """ + start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True, include_trailing_whitespace=True) + return TextObject(start, end) + + @text_object('a', 'p', no_move_handler=True) + def _(event): + """ + Auto paragraph. + """ + start = event.current_buffer.document.start_of_paragraph() + end = event.current_buffer.document.end_of_paragraph(count=event.arg) + return TextObject(start, end) + + @text_object('^') + def key_circumflex(event): + """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """ + return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=True)) + + @text_object('0') + def key_zero(event): + """ + 'c0', 'd0': Hard start of line, before whitespace. + (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) + """ + return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=False)) + + def create_ci_ca_handles(ci_start, ci_end, inner, key=None): + # TODO: 'dat', 'dit', (tags (like xml) + """ + Delete/Change string between this start and stop character. But keep these characters. + This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. + """ + def handler(event): + if ci_start == ci_end: + # Quotes + start = event.current_buffer.document.find_backwards(ci_start, in_current_line=False) + end = event.current_buffer.document.find(ci_end, in_current_line=False) + else: + # Brackets + start = event.current_buffer.document.find_enclosing_bracket_left(ci_start, ci_end) + end = event.current_buffer.document.find_enclosing_bracket_right(ci_start, ci_end) + + if start is not None and end is not None: + offset = 0 if inner else 1 + return TextObject(start + 1 - offset, end + offset) + else: + # Nothing found. + return TextObject(0) + + if key is None: + text_object('ai'[inner], ci_start, no_move_handler=True)(handler) + text_object('ai'[inner], ci_end, no_move_handler=True)(handler) + else: + text_object('ai'[inner], key, no_move_handler=True)(handler) + + for inner in (False, True): + for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"), + ('[', ']'), ('<', '>'), ('{', '}'), ('(', ')')]: + create_ci_ca_handles(ci_start, ci_end, inner) + + create_ci_ca_handles('(', ')', inner, 'b') # 'dab', 'dib' + create_ci_ca_handles('{', '}', inner, 'B') # 'daB', 'diB' + + @text_object('{') + def _(event): + """ + Move to previous blank-line separated section. + Implements '{', 'c{', 'd{', 'y{' + """ + index = event.current_buffer.document.start_of_paragraph( + count=event.arg, before=True) + return TextObject(index) + + @text_object('}') + def _(event): + """ + Move to next blank-line separated section. + Implements '}', 'c}', 'd}', 'y}' + """ + index = event.current_buffer.document.end_of_paragraph(count=event.arg, after=True) + return TextObject(index) + + @text_object('f', Keys.Any) + def _(event): + """ + Go to next occurance of character. Typing 'fx' will move the + cursor to the next occurance of character. 'x'. + """ + event.cli.vi_state.last_character_find = CharacterFind(event.data, False) + match = event.current_buffer.document.find( + event.data, in_current_line=True, count=event.arg) + if match: + return TextObject(match, type=TextObjectType.INCLUSIVE) + else: + return TextObject(0) + + @text_object('F', Keys.Any) + def _(event): + """ + Go to previous occurance of character. Typing 'Fx' will move the + cursor to the previous occurance of character. 'x'. + """ + event.cli.vi_state.last_character_find = CharacterFind(event.data, True) + return TextObject(event.current_buffer.document.find_backwards( + event.data, in_current_line=True, count=event.arg) or 0) + + @text_object('t', Keys.Any) + def _(event): + """ + Move right to the next occurance of c, then one char backward. + """ + event.cli.vi_state.last_character_find = CharacterFind(event.data, False) + match = event.current_buffer.document.find( + event.data, in_current_line=True, count=event.arg) + if match: + return TextObject(match - 1, type=TextObjectType.INCLUSIVE) + else: + return TextObject(0) + + @text_object('T', Keys.Any) + def _(event): + """ + Move left to the previous occurance of c, then one char forward. + """ + event.cli.vi_state.last_character_find = CharacterFind(event.data, True) + match = event.current_buffer.document.find_backwards( + event.data, in_current_line=True, count=event.arg) + return TextObject(match + 1 if match else 0) + + def repeat(reverse): + """ + Create ',' and ';' commands. + """ + @text_object(',' if reverse else ';') + def _(event): + # Repeat the last 'f'/'F'/'t'/'T' command. + pos = 0 + vi_state = event.cli.vi_state + + type = TextObjectType.EXCLUSIVE + + if vi_state.last_character_find: + char = vi_state.last_character_find.character + backwards = vi_state.last_character_find.backwards + + if reverse: + backwards = not backwards + + if backwards: + pos = event.current_buffer.document.find_backwards(char, in_current_line=True, count=event.arg) + else: + pos = event.current_buffer.document.find(char, in_current_line=True, count=event.arg) + type = TextObjectType.INCLUSIVE + if pos: + return TextObject(pos, type=type) + else: + return TextObject(0) + repeat(True) + repeat(False) + + @text_object('h') + @text_object(Keys.Left) + def _(event): + """ Implements 'ch', 'dh', 'h': Cursor left. """ + return TextObject(event.current_buffer.document.get_cursor_left_position(count=event.arg)) + + @text_object('j', no_move_handler=True, no_selection_handler=True) + # Note: We also need `no_selection_handler`, because we in + # selection mode, we prefer the other 'j' binding that keeps + # `buffer.preferred_column`. + def _(event): + """ Implements 'cj', 'dj', 'j', ... Cursor up. """ + return TextObject(event.current_buffer.document.get_cursor_down_position(count=event.arg), + type=TextObjectType.LINEWISE) + + @text_object('k', no_move_handler=True, no_selection_handler=True) + def _(event): + """ Implements 'ck', 'dk', 'k', ... Cursor up. """ + return TextObject(event.current_buffer.document.get_cursor_up_position(count=event.arg), + type=TextObjectType.LINEWISE) + + @text_object('l') + @text_object(' ') + @text_object(Keys.Right) + def _(event): + """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """ + return TextObject(event.current_buffer.document.get_cursor_right_position(count=event.arg)) + + @text_object('H') + def _(event): + """ + Moves to the start of the visible region. (Below the scroll offset.) + Implements 'cH', 'dH', 'H'. + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + b = event.current_buffer + + if w and w.render_info: + # When we find a Window that has BufferControl showing this window, + # move to the start of the visible area. + pos = (b.document.translate_row_col_to_index( + w.render_info.first_visible_line(after_scroll_offset=True), 0) - + b.cursor_position) + + else: + # Otherwise, move to the start of the input. + pos = -len(b.document.text_before_cursor) + return TextObject(pos, type=TextObjectType.LINEWISE) + + @text_object('M') + def _(event): + """ + Moves cursor to the vertical center of the visible region. + Implements 'cM', 'dM', 'M'. + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + b = event.current_buffer + + if w and w.render_info: + # When we find a Window that has BufferControl showing this window, + # move to the center of the visible area. + pos = (b.document.translate_row_col_to_index( + w.render_info.center_visible_line(), 0) - + b.cursor_position) + + else: + # Otherwise, move to the start of the input. + pos = -len(b.document.text_before_cursor) + return TextObject(pos, type=TextObjectType.LINEWISE) + + @text_object('L') + def _(event): + """ + Moves to the end of the visible region. (Above the scroll offset.) + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + b = event.current_buffer + + if w and w.render_info: + # When we find a Window that has BufferControl showing this window, + # move to the end of the visible area. + pos = (b.document.translate_row_col_to_index( + w.render_info.last_visible_line(before_scroll_offset=True), 0) - + b.cursor_position) + + else: + # Otherwise, move to the end of the input. + pos = len(b.document.text_after_cursor) + return TextObject(pos, type=TextObjectType.LINEWISE) + + @text_object('n', no_move_handler=True) + def _(event): + " Search next. " + buff = event.current_buffer + cursor_position = buff.get_search_position( + get_search_state(event.cli), include_current_position=False, + count=event.arg) + return TextObject(cursor_position - buff.cursor_position) + + @handle('n', filter=navigation_mode) + def _(event): + " Search next in navigation mode. (This goes through the history.) " + event.current_buffer.apply_search( + get_search_state(event.cli), include_current_position=False, + count=event.arg) + + @text_object('N', no_move_handler=True) + def _(event): + " Search previous. " + buff = event.current_buffer + cursor_position = buff.get_search_position( + ~get_search_state(event.cli), include_current_position=False, + count=event.arg) + return TextObject(cursor_position - buff.cursor_position) + + @handle('N', filter=navigation_mode) + def _(event): + " Search previous in navigation mode. (This goes through the history.) " + event.current_buffer.apply_search( + ~get_search_state(event.cli), include_current_position=False, + count=event.arg) + + @handle('z', '+', filter=navigation_mode|selection_mode) + @handle('z', 't', filter=navigation_mode|selection_mode) + @handle('z', Keys.ControlJ, filter=navigation_mode|selection_mode) + def _(event): + """ + Scrolls the window to makes the current line the first line in the visible region. + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + b = event.cli.current_buffer + w.vertical_scroll = b.document.cursor_position_row + + @handle('z', '-', filter=navigation_mode|selection_mode) + @handle('z', 'b', filter=navigation_mode|selection_mode) + def _(event): + """ + Scrolls the window to makes the current line the last line in the visible region. + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + + # We can safely set the scroll offset to zero; the Window will meke + # sure that it scrolls at least enough to make the cursor visible + # again. + w.vertical_scroll = 0 + + @handle('z', 'z', filter=navigation_mode|selection_mode) + def _(event): + """ + Center Window vertically around cursor. + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + b = event.cli.current_buffer + + if w and w.render_info: + info = w.render_info + + # Calculate the offset that we need in order to position the row + # containing the cursor in the center. + scroll_height = info.window_height // 2 + + y = max(0, b.document.cursor_position_row - 1) + height = 0 + while y > 0: + line_height = info.get_height_for_line(y) + + if height + line_height < scroll_height: + height += line_height + y -= 1 + else: + break + + w.vertical_scroll = y + + @text_object('%') + def _(event): + """ + Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) + If an 'arg' has been given, go this this % position in the file. + """ + buffer = event.current_buffer + + if event._arg: + # If 'arg' has been given, the meaning of % is to go to the 'x%' + # row in the file. + if 0 < event.arg <= 100: + absolute_index = buffer.document.translate_row_col_to_index( + int((event.arg * buffer.document.line_count - 1) / 100), 0) + return TextObject(absolute_index - buffer.document.cursor_position, type=TextObjectType.LINEWISE) + else: + return TextObject(0) # Do nothing. + + else: + # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). + match = buffer.document.find_matching_bracket_position() + if match: + return TextObject(match, type=TextObjectType.INCLUSIVE) + else: + return TextObject(0) + + @text_object('|') + def _(event): + # Move to the n-th column (you may specify the argument n by typing + # it on number keys, for example, 20|). + return TextObject(event.current_buffer.document.get_column_cursor_position(event.arg - 1)) + + @text_object('g', 'g') + def _(event): + """ + Implements 'gg', 'cgg', 'ygg' + """ + d = event.current_buffer.document + + if event._arg: + # Move to the given line. + return TextObject(d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, type=TextObjectType.LINEWISE) + else: + # Move to the top of the input. + return TextObject(d.get_start_of_document_position(), type=TextObjectType.LINEWISE) + + @text_object('g', '_') + def _(event): + """ + Go to last non-blank of line. + 'g_', 'cg_', 'yg_', etc.. + """ + return TextObject( + event.current_buffer.document.last_non_blank_of_current_line_position(), type=TextObjectType.INCLUSIVE) + + @text_object('g', 'e') + def _(event): + """ + Go to last character of previous word. + 'ge', 'cge', 'yge', etc.. + """ + prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg) + return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) + + @text_object('g', 'E') + def _(event): + """ + Go to last character of previous WORD. + 'gE', 'cgE', 'ygE', etc.. + """ + prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg, WORD=True) + return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) + + @text_object('g', 'm') + def _(event): + """ + Like g0, but half a screenwidth to the right. (Or as much as possible.) + """ + w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) + buff = event.current_buffer + + if w and w.render_info: + width = w.render_info.window_width + start = buff.document.get_start_of_line_position(after_whitespace=False) + start += int(min(width / 2, len(buff.document.current_line))) + + return TextObject(start, type=TextObjectType.INCLUSIVE) + return TextObject(0) + + @text_object('G') + def _(event): + """ + Go to the end of the document. (If no arg has been given.) + """ + buf = event.current_buffer + return TextObject(buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) - + buf.cursor_position, type=TextObjectType.LINEWISE) + + # + # *** Other *** + # + + @handle('G', filter=HasArg()) + def _(event): + """ + If an argument is given, move to this line in the history. (for + example, 15G) + """ + event.current_buffer.go_to_history(event.arg - 1) + + for n in '123456789': + @handle(n, filter=navigation_mode|selection_mode|operator_given) + def _(event): + """ + Always handle numberics in navigation mode as arg. + """ + event.append_to_arg_count(event.data) + + @handle('0', filter=(navigation_mode|selection_mode|operator_given) & HasArg()) + def _(event): + " Zero when an argument was already give. " + event.append_to_arg_count(event.data) + + @handle(Keys.Any, filter=replace_mode) + def _(event): + """ + Insert data at cursor position. + """ + event.current_buffer.insert_text(event.data, overwrite=True) + + @handle(Keys.Any, filter=insert_multiple_mode, + save_before=(lambda e: not e.is_repeat)) + def _(event): + """ + Insert data at multiple cursor positions at once. + (Usually a result of pressing 'I' or 'A' in block-selection mode.) + """ + buff = event.current_buffer + original_text = buff.text + + # Construct new text. + text = [] + p = 0 + + for p2 in buff.multiple_cursor_positions: + text.append(original_text[p:p2]) + text.append(event.data) + p = p2 + + text.append(original_text[p:]) + + # Shift all cursor positions. + new_cursor_positions = [ + p + i + 1 for i, p in enumerate(buff.multiple_cursor_positions)] + + # Set result. + buff.text = ''.join(text) + buff.multiple_cursor_positions = new_cursor_positions + buff.cursor_position += 1 + + @handle(Keys.Backspace, filter=insert_multiple_mode) + def _(event): + " Backspace, using multiple cursors. " + buff = event.current_buffer + original_text = buff.text + + # Construct new text. + deleted_something = False + text = [] + p = 0 + + for p2 in buff.multiple_cursor_positions: + if p2 > 0 and original_text[p2 - 1] != '\n': # Don't delete across lines. + text.append(original_text[p:p2 - 1]) + deleted_something = True + else: + text.append(original_text[p:p2]) + p = p2 + + text.append(original_text[p:]) + + if deleted_something: + # Shift all cursor positions. + lengths = [len(part) for part in text[:-1]] + new_cursor_positions = list(accumulate(lengths)) + + # Set result. + buff.text = ''.join(text) + buff.multiple_cursor_positions = new_cursor_positions + buff.cursor_position -= 1 + else: + event.cli.output.bell() + + @handle(Keys.Delete, filter=insert_multiple_mode) + def _(event): + " Delete, using multiple cursors. " + buff = event.current_buffer + original_text = buff.text + + # Construct new text. + deleted_something = False + text = [] + new_cursor_positions = [] + p = 0 + + for p2 in buff.multiple_cursor_positions: + text.append(original_text[p:p2]) + if p2 >= len(original_text) or original_text[p2] == '\n': + # Don't delete across lines. + p = p2 + else: + p = p2 + 1 + deleted_something = True + + text.append(original_text[p:]) + + if deleted_something: + # Shift all cursor positions. + lengths = [len(part) for part in text[:-1]] + new_cursor_positions = list(accumulate(lengths)) + + # Set result. + buff.text = ''.join(text) + buff.multiple_cursor_positions = new_cursor_positions + else: + event.cli.output.bell() + + + @handle(Keys.ControlX, Keys.ControlL, filter=insert_mode) + def _(event): + """ + Pressing the ControlX - ControlL sequence in Vi mode does line + completion based on the other lines in the document and the history. + """ + event.current_buffer.start_history_lines_completion() + + @handle(Keys.ControlX, Keys.ControlF, filter=insert_mode) + def _(event): + """ + Complete file names. + """ + # TODO + pass + + @handle(Keys.ControlK, filter=insert_mode|replace_mode) + def _(event): + " Go into digraph mode. " + event.cli.vi_state.waiting_for_digraph = True + + @Condition + def digraph_symbol_1_given(cli): + return cli.vi_state.digraph_symbol1 is not None + + @handle(Keys.Any, filter=digraph_mode & ~digraph_symbol_1_given) + def _(event): + event.cli.vi_state.digraph_symbol1 = event.data + + @handle(Keys.Any, filter=digraph_mode & digraph_symbol_1_given) + def _(event): + " Insert digraph. " + try: + # Lookup. + code = (event.cli.vi_state.digraph_symbol1, event.data) + if code not in DIGRAPHS: + code = code[::-1] # Try reversing. + symbol = DIGRAPHS[code] + except KeyError: + # Unkown digraph. + event.cli.output.bell() + else: + # Insert digraph. + overwrite = event.cli.vi_state.input_mode == InputMode.REPLACE + event.current_buffer.insert_text( + six.unichr(symbol), overwrite=overwrite) + event.cli.vi_state.waiting_for_digraph = False + finally: + event.cli.vi_state.waiting_for_digraph = False + event.cli.vi_state.digraph_symbol1 = None + + return registry + + +def load_vi_open_in_editor_bindings(): + """ + Pressing 'v' in navigation mode will open the buffer in an external editor. + """ + registry = Registry() + navigation_mode = ViNavigationMode() + + registry.add_binding('v', filter=navigation_mode)( + get_by_name('edit-and-execute-command')) + return registry + + +def load_vi_system_bindings(): + registry = ConditionalRegistry(Registry(), ViMode()) + handle = registry.add_binding + + has_focus = filters.HasFocus(SYSTEM_BUFFER) + navigation_mode = ViNavigationMode() + + @handle('!', filter=~has_focus & navigation_mode) + def _(event): + """ + '!' opens the system prompt. + """ + event.cli.push_focus(SYSTEM_BUFFER) + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle(Keys.Escape, filter=has_focus) + @handle(Keys.ControlC, filter=has_focus) + def _(event): + """ + Cancel system prompt. + """ + event.cli.vi_state.input_mode = InputMode.NAVIGATION + event.cli.buffers[SYSTEM_BUFFER].reset() + event.cli.pop_focus() + + @handle(Keys.ControlJ, filter=has_focus) + def _(event): + """ + Run system command. + """ + event.cli.vi_state.input_mode = InputMode.NAVIGATION + + system_buffer = event.cli.buffers[SYSTEM_BUFFER] + event.cli.run_system_command(system_buffer.text) + system_buffer.reset(append_to_history=True) + + # Focus previous buffer again. + event.cli.pop_focus() + + return registry + + +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() + + reverse_vi_search_direction = Condition( + lambda cli: cli.application.reverse_vi_search_direction(cli)) + + @handle('/', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction) + @handle('?', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction) + @handle(Keys.ControlS, filter=~has_focus) + def _(event): + """ + Vi-style forward search. + """ + # Set the ViState. + get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD + event.cli.vi_state.input_mode = InputMode.INSERT + + # Focus search buffer. + event.cli.push_focus(search_buffer_name) + + @handle('?', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction) + @handle('/', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction) + @handle(Keys.ControlR, filter=~has_focus) + def _(event): + """ + Vi-style backward search. + """ + # Set the ViState. + get_search_state(event.cli).direction = IncrementalSearchDirection.BACKWARD + + # Focus search buffer. + event.cli.push_focus(search_buffer_name) + event.cli.vi_state.input_mode = InputMode.INSERT + + @handle(Keys.ControlJ, filter=has_focus) + @handle(Keys.Escape, filter=has_focus) + def _(event): + """ + Apply the search. (At the / or ? prompt.) + """ + input_buffer = event.cli.buffers.previous(event.cli) + search_buffer = event.cli.buffers[search_buffer_name] + + # Update search state. + if search_buffer.text: + get_search_state(event.cli).text = search_buffer.text + + # Apply search. + input_buffer.apply_search(get_search_state(event.cli)) + + # Add query to history of search line. + search_buffer.append_to_history() + search_buffer.reset() + + # Focus previous document again. + event.cli.vi_state.input_mode = InputMode.NAVIGATION + event.cli.pop_focus() + + def incremental_search(cli, direction, count=1): + " Apply search, but keep search buffer focussed. " + # Update search_state. + search_state = get_search_state(cli) + direction_changed = search_state.direction != direction + + search_state.text = cli.buffers[search_buffer_name].text + search_state.direction = direction + + # Apply search to current buffer. + if not direction_changed: + input_buffer = cli.buffers.previous(cli) + input_buffer.apply_search(search_state, + include_current_position=False, count=count) + + @handle(Keys.ControlR, filter=has_focus) + def _(event): + incremental_search(event.cli, IncrementalSearchDirection.BACKWARD, count=event.arg) + + @handle(Keys.ControlS, filter=has_focus) + def _(event): + incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg) + + def search_buffer_is_empty(cli): + """ Returns True when the search buffer is empty. """ + return cli.buffers[search_buffer_name].text == '' + + @handle(Keys.ControlC, filter=has_focus) + @handle(Keys.ControlH, filter=has_focus & Condition(search_buffer_is_empty)) + @handle(Keys.Backspace, filter=has_focus & Condition(search_buffer_is_empty)) + def _(event): + """ + Cancel search. + """ + event.cli.vi_state.input_mode = InputMode.NAVIGATION + + event.cli.pop_focus() + event.cli.buffers[search_buffer_name].reset() + + return registry + + +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. + """ + registry = ConditionalRegistry(Registry(), ViMode()) + handle = registry.add_binding + + handle(Keys.ControlF)(scroll_forward) + handle(Keys.ControlB)(scroll_backward) + handle(Keys.ControlD)(scroll_half_page_down) + handle(Keys.ControlU)(scroll_half_page_up) + handle(Keys.ControlE)(scroll_one_line_down) + handle(Keys.ControlY)(scroll_one_line_up) + handle(Keys.PageDown)(scroll_page_down) + handle(Keys.PageUp)(scroll_page_up) + + return registry + + +class ViStateFilter(Filter): + " Deprecated! " + def __init__(self, get_vi_state, mode): + self.get_vi_state = get_vi_state + self.mode = mode + + def __call__(self, cli): + return self.get_vi_state(cli).input_mode == self.mode diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/defaults.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/defaults.py new file mode 100644 index 00000000000..fb2c1070f79 --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/defaults.py @@ -0,0 +1,119 @@ +""" +Default key bindings.:: + + registry = load_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_key_bindings', + 'load_key_bindings_for_prompt', +) + + +def load_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(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_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_key_bindings(**kw) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/digraphs.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/digraphs.py new file mode 100644 index 00000000000..36c6b15103a --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/digraphs.py @@ -0,0 +1,1378 @@ +# encoding: utf-8 +from __future__ import unicode_literals +""" +Vi Digraphs. +This is a list of special characters that can be inserted in Vi insert mode by +pressing Control-K followed by to normal characters. + +Taken from Neovim and translated to Python: +https://raw.githubusercontent.com/neovim/neovim/master/src/nvim/digraph.c +""" +__all__ = ('DIGRAPHS', ) + +# digraphs for Unicode from RFC1345 +# (also work for ISO-8859-1 aka latin1) +DIGRAPHS = { + ('N', 'U'): 0x00, + ('S', 'H'): 0x01, + ('S', 'X'): 0x02, + ('E', 'X'): 0x03, + ('E', 'T'): 0x04, + ('E', 'Q'): 0x05, + ('A', 'K'): 0x06, + ('B', 'L'): 0x07, + ('B', 'S'): 0x08, + ('H', 'T'): 0x09, + ('L', 'F'): 0x0a, + ('V', 'T'): 0x0b, + ('F', 'F'): 0x0c, + ('C', 'R'): 0x0d, + ('S', 'O'): 0x0e, + ('S', 'I'): 0x0f, + ('D', 'L'): 0x10, + ('D', '1'): 0x11, + ('D', '2'): 0x12, + ('D', '3'): 0x13, + ('D', '4'): 0x14, + ('N', 'K'): 0x15, + ('S', 'Y'): 0x16, + ('E', 'B'): 0x17, + ('C', 'N'): 0x18, + ('E', 'M'): 0x19, + ('S', 'B'): 0x1a, + ('E', 'C'): 0x1b, + ('F', 'S'): 0x1c, + ('G', 'S'): 0x1d, + ('R', 'S'): 0x1e, + ('U', 'S'): 0x1f, + ('S', 'P'): 0x20, + ('N', 'b'): 0x23, + ('D', 'O'): 0x24, + ('A', 't'): 0x40, + ('<', '('): 0x5b, + ('/', '/'): 0x5c, + (')', '>'): 0x5d, + ('\'', '>'): 0x5e, + ('\'', '!'): 0x60, + ('(', '!'): 0x7b, + ('!', '!'): 0x7c, + ('!', ')'): 0x7d, + ('\'', '?'): 0x7e, + ('D', 'T'): 0x7f, + ('P', 'A'): 0x80, + ('H', 'O'): 0x81, + ('B', 'H'): 0x82, + ('N', 'H'): 0x83, + ('I', 'N'): 0x84, + ('N', 'L'): 0x85, + ('S', 'A'): 0x86, + ('E', 'S'): 0x87, + ('H', 'S'): 0x88, + ('H', 'J'): 0x89, + ('V', 'S'): 0x8a, + ('P', 'D'): 0x8b, + ('P', 'U'): 0x8c, + ('R', 'I'): 0x8d, + ('S', '2'): 0x8e, + ('S', '3'): 0x8f, + ('D', 'C'): 0x90, + ('P', '1'): 0x91, + ('P', '2'): 0x92, + ('T', 'S'): 0x93, + ('C', 'C'): 0x94, + ('M', 'W'): 0x95, + ('S', 'G'): 0x96, + ('E', 'G'): 0x97, + ('S', 'S'): 0x98, + ('G', 'C'): 0x99, + ('S', 'C'): 0x9a, + ('C', 'I'): 0x9b, + ('S', 'T'): 0x9c, + ('O', 'C'): 0x9d, + ('P', 'M'): 0x9e, + ('A', 'C'): 0x9f, + ('N', 'S'): 0xa0, + ('!', 'I'): 0xa1, + ('C', 't'): 0xa2, + ('P', 'd'): 0xa3, + ('C', 'u'): 0xa4, + ('Y', 'e'): 0xa5, + ('B', 'B'): 0xa6, + ('S', 'E'): 0xa7, + ('\'', ':'): 0xa8, + ('C', 'o'): 0xa9, + ('-', 'a'): 0xaa, + ('<', '<'): 0xab, + ('N', 'O'): 0xac, + ('-', '-'): 0xad, + ('R', 'g'): 0xae, + ('\'', 'm'): 0xaf, + ('D', 'G'): 0xb0, + ('+', '-'): 0xb1, + ('2', 'S'): 0xb2, + ('3', 'S'): 0xb3, + ('\'', '\''): 0xb4, + ('M', 'y'): 0xb5, + ('P', 'I'): 0xb6, + ('.', 'M'): 0xb7, + ('\'', ','): 0xb8, + ('1', 'S'): 0xb9, + ('-', 'o'): 0xba, + ('>', '>'): 0xbb, + ('1', '4'): 0xbc, + ('1', '2'): 0xbd, + ('3', '4'): 0xbe, + ('?', 'I'): 0xbf, + ('A', '!'): 0xc0, + ('A', '\''): 0xc1, + ('A', '>'): 0xc2, + ('A', '?'): 0xc3, + ('A', ':'): 0xc4, + ('A', 'A'): 0xc5, + ('A', 'E'): 0xc6, + ('C', ','): 0xc7, + ('E', '!'): 0xc8, + ('E', '\''): 0xc9, + ('E', '>'): 0xca, + ('E', ':'): 0xcb, + ('I', '!'): 0xcc, + ('I', '\''): 0xcd, + ('I', '>'): 0xce, + ('I', ':'): 0xcf, + ('D', '-'): 0xd0, + ('N', '?'): 0xd1, + ('O', '!'): 0xd2, + ('O', '\''): 0xd3, + ('O', '>'): 0xd4, + ('O', '?'): 0xd5, + ('O', ':'): 0xd6, + ('*', 'X'): 0xd7, + ('O', '/'): 0xd8, + ('U', '!'): 0xd9, + ('U', '\''): 0xda, + ('U', '>'): 0xdb, + ('U', ':'): 0xdc, + ('Y', '\''): 0xdd, + ('T', 'H'): 0xde, + ('s', 's'): 0xdf, + ('a', '!'): 0xe0, + ('a', '\''): 0xe1, + ('a', '>'): 0xe2, + ('a', '?'): 0xe3, + ('a', ':'): 0xe4, + ('a', 'a'): 0xe5, + ('a', 'e'): 0xe6, + ('c', ','): 0xe7, + ('e', '!'): 0xe8, + ('e', '\''): 0xe9, + ('e', '>'): 0xea, + ('e', ':'): 0xeb, + ('i', '!'): 0xec, + ('i', '\''): 0xed, + ('i', '>'): 0xee, + ('i', ':'): 0xef, + ('d', '-'): 0xf0, + ('n', '?'): 0xf1, + ('o', '!'): 0xf2, + ('o', '\''): 0xf3, + ('o', '>'): 0xf4, + ('o', '?'): 0xf5, + ('o', ':'): 0xf6, + ('-', ':'): 0xf7, + ('o', '/'): 0xf8, + ('u', '!'): 0xf9, + ('u', '\''): 0xfa, + ('u', '>'): 0xfb, + ('u', ':'): 0xfc, + ('y', '\''): 0xfd, + ('t', 'h'): 0xfe, + ('y', ':'): 0xff, + + ('A', '-'): 0x0100, + ('a', '-'): 0x0101, + ('A', '('): 0x0102, + ('a', '('): 0x0103, + ('A', ';'): 0x0104, + ('a', ';'): 0x0105, + ('C', '\''): 0x0106, + ('c', '\''): 0x0107, + ('C', '>'): 0x0108, + ('c', '>'): 0x0109, + ('C', '.'): 0x010a, + ('c', '.'): 0x010b, + ('C', '<'): 0x010c, + ('c', '<'): 0x010d, + ('D', '<'): 0x010e, + ('d', '<'): 0x010f, + ('D', '/'): 0x0110, + ('d', '/'): 0x0111, + ('E', '-'): 0x0112, + ('e', '-'): 0x0113, + ('E', '('): 0x0114, + ('e', '('): 0x0115, + ('E', '.'): 0x0116, + ('e', '.'): 0x0117, + ('E', ';'): 0x0118, + ('e', ';'): 0x0119, + ('E', '<'): 0x011a, + ('e', '<'): 0x011b, + ('G', '>'): 0x011c, + ('g', '>'): 0x011d, + ('G', '('): 0x011e, + ('g', '('): 0x011f, + ('G', '.'): 0x0120, + ('g', '.'): 0x0121, + ('G', ','): 0x0122, + ('g', ','): 0x0123, + ('H', '>'): 0x0124, + ('h', '>'): 0x0125, + ('H', '/'): 0x0126, + ('h', '/'): 0x0127, + ('I', '?'): 0x0128, + ('i', '?'): 0x0129, + ('I', '-'): 0x012a, + ('i', '-'): 0x012b, + ('I', '('): 0x012c, + ('i', '('): 0x012d, + ('I', ';'): 0x012e, + ('i', ';'): 0x012f, + ('I', '.'): 0x0130, + ('i', '.'): 0x0131, + ('I', 'J'): 0x0132, + ('i', 'j'): 0x0133, + ('J', '>'): 0x0134, + ('j', '>'): 0x0135, + ('K', ','): 0x0136, + ('k', ','): 0x0137, + ('k', 'k'): 0x0138, + ('L', '\''): 0x0139, + ('l', '\''): 0x013a, + ('L', ','): 0x013b, + ('l', ','): 0x013c, + ('L', '<'): 0x013d, + ('l', '<'): 0x013e, + ('L', '.'): 0x013f, + ('l', '.'): 0x0140, + ('L', '/'): 0x0141, + ('l', '/'): 0x0142, + ('N', '\''): 0x0143, + ('n', '\''): 0x0144, + ('N', ','): 0x0145, + ('n', ','): 0x0146, + ('N', '<'): 0x0147, + ('n', '<'): 0x0148, + ('\'', 'n'): 0x0149, + ('N', 'G'): 0x014a, + ('n', 'g'): 0x014b, + ('O', '-'): 0x014c, + ('o', '-'): 0x014d, + ('O', '('): 0x014e, + ('o', '('): 0x014f, + ('O', '"'): 0x0150, + ('o', '"'): 0x0151, + ('O', 'E'): 0x0152, + ('o', 'e'): 0x0153, + ('R', '\''): 0x0154, + ('r', '\''): 0x0155, + ('R', ','): 0x0156, + ('r', ','): 0x0157, + ('R', '<'): 0x0158, + ('r', '<'): 0x0159, + ('S', '\''): 0x015a, + ('s', '\''): 0x015b, + ('S', '>'): 0x015c, + ('s', '>'): 0x015d, + ('S', ','): 0x015e, + ('s', ','): 0x015f, + ('S', '<'): 0x0160, + ('s', '<'): 0x0161, + ('T', ','): 0x0162, + ('t', ','): 0x0163, + ('T', '<'): 0x0164, + ('t', '<'): 0x0165, + ('T', '/'): 0x0166, + ('t', '/'): 0x0167, + ('U', '?'): 0x0168, + ('u', '?'): 0x0169, + ('U', '-'): 0x016a, + ('u', '-'): 0x016b, + ('U', '('): 0x016c, + ('u', '('): 0x016d, + ('U', '0'): 0x016e, + ('u', '0'): 0x016f, + ('U', '"'): 0x0170, + ('u', '"'): 0x0171, + ('U', ';'): 0x0172, + ('u', ';'): 0x0173, + ('W', '>'): 0x0174, + ('w', '>'): 0x0175, + ('Y', '>'): 0x0176, + ('y', '>'): 0x0177, + ('Y', ':'): 0x0178, + ('Z', '\''): 0x0179, + ('z', '\''): 0x017a, + ('Z', '.'): 0x017b, + ('z', '.'): 0x017c, + ('Z', '<'): 0x017d, + ('z', '<'): 0x017e, + ('O', '9'): 0x01a0, + ('o', '9'): 0x01a1, + ('O', 'I'): 0x01a2, + ('o', 'i'): 0x01a3, + ('y', 'r'): 0x01a6, + ('U', '9'): 0x01af, + ('u', '9'): 0x01b0, + ('Z', '/'): 0x01b5, + ('z', '/'): 0x01b6, + ('E', 'D'): 0x01b7, + ('A', '<'): 0x01cd, + ('a', '<'): 0x01ce, + ('I', '<'): 0x01cf, + ('i', '<'): 0x01d0, + ('O', '<'): 0x01d1, + ('o', '<'): 0x01d2, + ('U', '<'): 0x01d3, + ('u', '<'): 0x01d4, + ('A', '1'): 0x01de, + ('a', '1'): 0x01df, + ('A', '7'): 0x01e0, + ('a', '7'): 0x01e1, + ('A', '3'): 0x01e2, + ('a', '3'): 0x01e3, + ('G', '/'): 0x01e4, + ('g', '/'): 0x01e5, + ('G', '<'): 0x01e6, + ('g', '<'): 0x01e7, + ('K', '<'): 0x01e8, + ('k', '<'): 0x01e9, + ('O', ';'): 0x01ea, + ('o', ';'): 0x01eb, + ('O', '1'): 0x01ec, + ('o', '1'): 0x01ed, + ('E', 'Z'): 0x01ee, + ('e', 'z'): 0x01ef, + ('j', '<'): 0x01f0, + ('G', '\''): 0x01f4, + ('g', '\''): 0x01f5, + (';', 'S'): 0x02bf, + ('\'', '<'): 0x02c7, + ('\'', '('): 0x02d8, + ('\'', '.'): 0x02d9, + ('\'', '0'): 0x02da, + ('\'', ';'): 0x02db, + ('\'', '"'): 0x02dd, + ('A', '%'): 0x0386, + ('E', '%'): 0x0388, + ('Y', '%'): 0x0389, + ('I', '%'): 0x038a, + ('O', '%'): 0x038c, + ('U', '%'): 0x038e, + ('W', '%'): 0x038f, + ('i', '3'): 0x0390, + ('A', '*'): 0x0391, + ('B', '*'): 0x0392, + ('G', '*'): 0x0393, + ('D', '*'): 0x0394, + ('E', '*'): 0x0395, + ('Z', '*'): 0x0396, + ('Y', '*'): 0x0397, + ('H', '*'): 0x0398, + ('I', '*'): 0x0399, + ('K', '*'): 0x039a, + ('L', '*'): 0x039b, + ('M', '*'): 0x039c, + ('N', '*'): 0x039d, + ('C', '*'): 0x039e, + ('O', '*'): 0x039f, + ('P', '*'): 0x03a0, + ('R', '*'): 0x03a1, + ('S', '*'): 0x03a3, + ('T', '*'): 0x03a4, + ('U', '*'): 0x03a5, + ('F', '*'): 0x03a6, + ('X', '*'): 0x03a7, + ('Q', '*'): 0x03a8, + ('W', '*'): 0x03a9, + ('J', '*'): 0x03aa, + ('V', '*'): 0x03ab, + ('a', '%'): 0x03ac, + ('e', '%'): 0x03ad, + ('y', '%'): 0x03ae, + ('i', '%'): 0x03af, + ('u', '3'): 0x03b0, + ('a', '*'): 0x03b1, + ('b', '*'): 0x03b2, + ('g', '*'): 0x03b3, + ('d', '*'): 0x03b4, + ('e', '*'): 0x03b5, + ('z', '*'): 0x03b6, + ('y', '*'): 0x03b7, + ('h', '*'): 0x03b8, + ('i', '*'): 0x03b9, + ('k', '*'): 0x03ba, + ('l', '*'): 0x03bb, + ('m', '*'): 0x03bc, + ('n', '*'): 0x03bd, + ('c', '*'): 0x03be, + ('o', '*'): 0x03bf, + ('p', '*'): 0x03c0, + ('r', '*'): 0x03c1, + ('*', 's'): 0x03c2, + ('s', '*'): 0x03c3, + ('t', '*'): 0x03c4, + ('u', '*'): 0x03c5, + ('f', '*'): 0x03c6, + ('x', '*'): 0x03c7, + ('q', '*'): 0x03c8, + ('w', '*'): 0x03c9, + ('j', '*'): 0x03ca, + ('v', '*'): 0x03cb, + ('o', '%'): 0x03cc, + ('u', '%'): 0x03cd, + ('w', '%'): 0x03ce, + ('\'', 'G'): 0x03d8, + (',', 'G'): 0x03d9, + ('T', '3'): 0x03da, + ('t', '3'): 0x03db, + ('M', '3'): 0x03dc, + ('m', '3'): 0x03dd, + ('K', '3'): 0x03de, + ('k', '3'): 0x03df, + ('P', '3'): 0x03e0, + ('p', '3'): 0x03e1, + ('\'', '%'): 0x03f4, + ('j', '3'): 0x03f5, + ('I', 'O'): 0x0401, + ('D', '%'): 0x0402, + ('G', '%'): 0x0403, + ('I', 'E'): 0x0404, + ('D', 'S'): 0x0405, + ('I', 'I'): 0x0406, + ('Y', 'I'): 0x0407, + ('J', '%'): 0x0408, + ('L', 'J'): 0x0409, + ('N', 'J'): 0x040a, + ('T', 's'): 0x040b, + ('K', 'J'): 0x040c, + ('V', '%'): 0x040e, + ('D', 'Z'): 0x040f, + ('A', '='): 0x0410, + ('B', '='): 0x0411, + ('V', '='): 0x0412, + ('G', '='): 0x0413, + ('D', '='): 0x0414, + ('E', '='): 0x0415, + ('Z', '%'): 0x0416, + ('Z', '='): 0x0417, + ('I', '='): 0x0418, + ('J', '='): 0x0419, + ('K', '='): 0x041a, + ('L', '='): 0x041b, + ('M', '='): 0x041c, + ('N', '='): 0x041d, + ('O', '='): 0x041e, + ('P', '='): 0x041f, + ('R', '='): 0x0420, + ('S', '='): 0x0421, + ('T', '='): 0x0422, + ('U', '='): 0x0423, + ('F', '='): 0x0424, + ('H', '='): 0x0425, + ('C', '='): 0x0426, + ('C', '%'): 0x0427, + ('S', '%'): 0x0428, + ('S', 'c'): 0x0429, + ('=', '"'): 0x042a, + ('Y', '='): 0x042b, + ('%', '"'): 0x042c, + ('J', 'E'): 0x042d, + ('J', 'U'): 0x042e, + ('J', 'A'): 0x042f, + ('a', '='): 0x0430, + ('b', '='): 0x0431, + ('v', '='): 0x0432, + ('g', '='): 0x0433, + ('d', '='): 0x0434, + ('e', '='): 0x0435, + ('z', '%'): 0x0436, + ('z', '='): 0x0437, + ('i', '='): 0x0438, + ('j', '='): 0x0439, + ('k', '='): 0x043a, + ('l', '='): 0x043b, + ('m', '='): 0x043c, + ('n', '='): 0x043d, + ('o', '='): 0x043e, + ('p', '='): 0x043f, + ('r', '='): 0x0440, + ('s', '='): 0x0441, + ('t', '='): 0x0442, + ('u', '='): 0x0443, + ('f', '='): 0x0444, + ('h', '='): 0x0445, + ('c', '='): 0x0446, + ('c', '%'): 0x0447, + ('s', '%'): 0x0448, + ('s', 'c'): 0x0449, + ('=', '\''): 0x044a, + ('y', '='): 0x044b, + ('%', '\''): 0x044c, + ('j', 'e'): 0x044d, + ('j', 'u'): 0x044e, + ('j', 'a'): 0x044f, + ('i', 'o'): 0x0451, + ('d', '%'): 0x0452, + ('g', '%'): 0x0453, + ('i', 'e'): 0x0454, + ('d', 's'): 0x0455, + ('i', 'i'): 0x0456, + ('y', 'i'): 0x0457, + ('j', '%'): 0x0458, + ('l', 'j'): 0x0459, + ('n', 'j'): 0x045a, + ('t', 's'): 0x045b, + ('k', 'j'): 0x045c, + ('v', '%'): 0x045e, + ('d', 'z'): 0x045f, + ('Y', '3'): 0x0462, + ('y', '3'): 0x0463, + ('O', '3'): 0x046a, + ('o', '3'): 0x046b, + ('F', '3'): 0x0472, + ('f', '3'): 0x0473, + ('V', '3'): 0x0474, + ('v', '3'): 0x0475, + ('C', '3'): 0x0480, + ('c', '3'): 0x0481, + ('G', '3'): 0x0490, + ('g', '3'): 0x0491, + ('A', '+'): 0x05d0, + ('B', '+'): 0x05d1, + ('G', '+'): 0x05d2, + ('D', '+'): 0x05d3, + ('H', '+'): 0x05d4, + ('W', '+'): 0x05d5, + ('Z', '+'): 0x05d6, + ('X', '+'): 0x05d7, + ('T', 'j'): 0x05d8, + ('J', '+'): 0x05d9, + ('K', '%'): 0x05da, + ('K', '+'): 0x05db, + ('L', '+'): 0x05dc, + ('M', '%'): 0x05dd, + ('M', '+'): 0x05de, + ('N', '%'): 0x05df, + ('N', '+'): 0x05e0, + ('S', '+'): 0x05e1, + ('E', '+'): 0x05e2, + ('P', '%'): 0x05e3, + ('P', '+'): 0x05e4, + ('Z', 'j'): 0x05e5, + ('Z', 'J'): 0x05e6, + ('Q', '+'): 0x05e7, + ('R', '+'): 0x05e8, + ('S', 'h'): 0x05e9, + ('T', '+'): 0x05ea, + (',', '+'): 0x060c, + (';', '+'): 0x061b, + ('?', '+'): 0x061f, + ('H', '\''): 0x0621, + ('a', 'M'): 0x0622, + ('a', 'H'): 0x0623, + ('w', 'H'): 0x0624, + ('a', 'h'): 0x0625, + ('y', 'H'): 0x0626, + ('a', '+'): 0x0627, + ('b', '+'): 0x0628, + ('t', 'm'): 0x0629, + ('t', '+'): 0x062a, + ('t', 'k'): 0x062b, + ('g', '+'): 0x062c, + ('h', 'k'): 0x062d, + ('x', '+'): 0x062e, + ('d', '+'): 0x062f, + ('d', 'k'): 0x0630, + ('r', '+'): 0x0631, + ('z', '+'): 0x0632, + ('s', '+'): 0x0633, + ('s', 'n'): 0x0634, + ('c', '+'): 0x0635, + ('d', 'd'): 0x0636, + ('t', 'j'): 0x0637, + ('z', 'H'): 0x0638, + ('e', '+'): 0x0639, + ('i', '+'): 0x063a, + ('+', '+'): 0x0640, + ('f', '+'): 0x0641, + ('q', '+'): 0x0642, + ('k', '+'): 0x0643, + ('l', '+'): 0x0644, + ('m', '+'): 0x0645, + ('n', '+'): 0x0646, + ('h', '+'): 0x0647, + ('w', '+'): 0x0648, + ('j', '+'): 0x0649, + ('y', '+'): 0x064a, + (':', '+'): 0x064b, + ('"', '+'): 0x064c, + ('=', '+'): 0x064d, + ('/', '+'): 0x064e, + ('\'', '+'): 0x064f, + ('1', '+'): 0x0650, + ('3', '+'): 0x0651, + ('0', '+'): 0x0652, + ('a', 'S'): 0x0670, + ('p', '+'): 0x067e, + ('v', '+'): 0x06a4, + ('g', 'f'): 0x06af, + ('0', 'a'): 0x06f0, + ('1', 'a'): 0x06f1, + ('2', 'a'): 0x06f2, + ('3', 'a'): 0x06f3, + ('4', 'a'): 0x06f4, + ('5', 'a'): 0x06f5, + ('6', 'a'): 0x06f6, + ('7', 'a'): 0x06f7, + ('8', 'a'): 0x06f8, + ('9', 'a'): 0x06f9, + ('B', '.'): 0x1e02, + ('b', '.'): 0x1e03, + ('B', '_'): 0x1e06, + ('b', '_'): 0x1e07, + ('D', '.'): 0x1e0a, + ('d', '.'): 0x1e0b, + ('D', '_'): 0x1e0e, + ('d', '_'): 0x1e0f, + ('D', ','): 0x1e10, + ('d', ','): 0x1e11, + ('F', '.'): 0x1e1e, + ('f', '.'): 0x1e1f, + ('G', '-'): 0x1e20, + ('g', '-'): 0x1e21, + ('H', '.'): 0x1e22, + ('h', '.'): 0x1e23, + ('H', ':'): 0x1e26, + ('h', ':'): 0x1e27, + ('H', ','): 0x1e28, + ('h', ','): 0x1e29, + ('K', '\''): 0x1e30, + ('k', '\''): 0x1e31, + ('K', '_'): 0x1e34, + ('k', '_'): 0x1e35, + ('L', '_'): 0x1e3a, + ('l', '_'): 0x1e3b, + ('M', '\''): 0x1e3e, + ('m', '\''): 0x1e3f, + ('M', '.'): 0x1e40, + ('m', '.'): 0x1e41, + ('N', '.'): 0x1e44, + ('n', '.'): 0x1e45, + ('N', '_'): 0x1e48, + ('n', '_'): 0x1e49, + ('P', '\''): 0x1e54, + ('p', '\''): 0x1e55, + ('P', '.'): 0x1e56, + ('p', '.'): 0x1e57, + ('R', '.'): 0x1e58, + ('r', '.'): 0x1e59, + ('R', '_'): 0x1e5e, + ('r', '_'): 0x1e5f, + ('S', '.'): 0x1e60, + ('s', '.'): 0x1e61, + ('T', '.'): 0x1e6a, + ('t', '.'): 0x1e6b, + ('T', '_'): 0x1e6e, + ('t', '_'): 0x1e6f, + ('V', '?'): 0x1e7c, + ('v', '?'): 0x1e7d, + ('W', '!'): 0x1e80, + ('w', '!'): 0x1e81, + ('W', '\''): 0x1e82, + ('w', '\''): 0x1e83, + ('W', ':'): 0x1e84, + ('w', ':'): 0x1e85, + ('W', '.'): 0x1e86, + ('w', '.'): 0x1e87, + ('X', '.'): 0x1e8a, + ('x', '.'): 0x1e8b, + ('X', ':'): 0x1e8c, + ('x', ':'): 0x1e8d, + ('Y', '.'): 0x1e8e, + ('y', '.'): 0x1e8f, + ('Z', '>'): 0x1e90, + ('z', '>'): 0x1e91, + ('Z', '_'): 0x1e94, + ('z', '_'): 0x1e95, + ('h', '_'): 0x1e96, + ('t', ':'): 0x1e97, + ('w', '0'): 0x1e98, + ('y', '0'): 0x1e99, + ('A', '2'): 0x1ea2, + ('a', '2'): 0x1ea3, + ('E', '2'): 0x1eba, + ('e', '2'): 0x1ebb, + ('E', '?'): 0x1ebc, + ('e', '?'): 0x1ebd, + ('I', '2'): 0x1ec8, + ('i', '2'): 0x1ec9, + ('O', '2'): 0x1ece, + ('o', '2'): 0x1ecf, + ('U', '2'): 0x1ee6, + ('u', '2'): 0x1ee7, + ('Y', '!'): 0x1ef2, + ('y', '!'): 0x1ef3, + ('Y', '2'): 0x1ef6, + ('y', '2'): 0x1ef7, + ('Y', '?'): 0x1ef8, + ('y', '?'): 0x1ef9, + (';', '\''): 0x1f00, + (',', '\''): 0x1f01, + (';', '!'): 0x1f02, + (',', '!'): 0x1f03, + ('?', ';'): 0x1f04, + ('?', ','): 0x1f05, + ('!', ':'): 0x1f06, + ('?', ':'): 0x1f07, + ('1', 'N'): 0x2002, + ('1', 'M'): 0x2003, + ('3', 'M'): 0x2004, + ('4', 'M'): 0x2005, + ('6', 'M'): 0x2006, + ('1', 'T'): 0x2009, + ('1', 'H'): 0x200a, + ('-', '1'): 0x2010, + ('-', 'N'): 0x2013, + ('-', 'M'): 0x2014, + ('-', '3'): 0x2015, + ('!', '2'): 0x2016, + ('=', '2'): 0x2017, + ('\'', '6'): 0x2018, + ('\'', '9'): 0x2019, + ('.', '9'): 0x201a, + ('9', '\''): 0x201b, + ('"', '6'): 0x201c, + ('"', '9'): 0x201d, + (':', '9'): 0x201e, + ('9', '"'): 0x201f, + ('/', '-'): 0x2020, + ('/', '='): 0x2021, + ('.', '.'): 0x2025, + ('%', '0'): 0x2030, + ('1', '\''): 0x2032, + ('2', '\''): 0x2033, + ('3', '\''): 0x2034, + ('1', '"'): 0x2035, + ('2', '"'): 0x2036, + ('3', '"'): 0x2037, + ('C', 'a'): 0x2038, + ('<', '1'): 0x2039, + ('>', '1'): 0x203a, + (':', 'X'): 0x203b, + ('\'', '-'): 0x203e, + ('/', 'f'): 0x2044, + ('0', 'S'): 0x2070, + ('4', 'S'): 0x2074, + ('5', 'S'): 0x2075, + ('6', 'S'): 0x2076, + ('7', 'S'): 0x2077, + ('8', 'S'): 0x2078, + ('9', 'S'): 0x2079, + ('+', 'S'): 0x207a, + ('-', 'S'): 0x207b, + ('=', 'S'): 0x207c, + ('(', 'S'): 0x207d, + (')', 'S'): 0x207e, + ('n', 'S'): 0x207f, + ('0', 's'): 0x2080, + ('1', 's'): 0x2081, + ('2', 's'): 0x2082, + ('3', 's'): 0x2083, + ('4', 's'): 0x2084, + ('5', 's'): 0x2085, + ('6', 's'): 0x2086, + ('7', 's'): 0x2087, + ('8', 's'): 0x2088, + ('9', 's'): 0x2089, + ('+', 's'): 0x208a, + ('-', 's'): 0x208b, + ('=', 's'): 0x208c, + ('(', 's'): 0x208d, + (')', 's'): 0x208e, + ('L', 'i'): 0x20a4, + ('P', 't'): 0x20a7, + ('W', '='): 0x20a9, + ('=', 'e'): 0x20ac, # euro + ('E', 'u'): 0x20ac, # euro + ('=', 'R'): 0x20bd, # rouble + ('=', 'P'): 0x20bd, # rouble + ('o', 'C'): 0x2103, + ('c', 'o'): 0x2105, + ('o', 'F'): 0x2109, + ('N', '0'): 0x2116, + ('P', 'O'): 0x2117, + ('R', 'x'): 0x211e, + ('S', 'M'): 0x2120, + ('T', 'M'): 0x2122, + ('O', 'm'): 0x2126, + ('A', 'O'): 0x212b, + ('1', '3'): 0x2153, + ('2', '3'): 0x2154, + ('1', '5'): 0x2155, + ('2', '5'): 0x2156, + ('3', '5'): 0x2157, + ('4', '5'): 0x2158, + ('1', '6'): 0x2159, + ('5', '6'): 0x215a, + ('1', '8'): 0x215b, + ('3', '8'): 0x215c, + ('5', '8'): 0x215d, + ('7', '8'): 0x215e, + ('1', 'R'): 0x2160, + ('2', 'R'): 0x2161, + ('3', 'R'): 0x2162, + ('4', 'R'): 0x2163, + ('5', 'R'): 0x2164, + ('6', 'R'): 0x2165, + ('7', 'R'): 0x2166, + ('8', 'R'): 0x2167, + ('9', 'R'): 0x2168, + ('a', 'R'): 0x2169, + ('b', 'R'): 0x216a, + ('c', 'R'): 0x216b, + ('1', 'r'): 0x2170, + ('2', 'r'): 0x2171, + ('3', 'r'): 0x2172, + ('4', 'r'): 0x2173, + ('5', 'r'): 0x2174, + ('6', 'r'): 0x2175, + ('7', 'r'): 0x2176, + ('8', 'r'): 0x2177, + ('9', 'r'): 0x2178, + ('a', 'r'): 0x2179, + ('b', 'r'): 0x217a, + ('c', 'r'): 0x217b, + ('<', '-'): 0x2190, + ('-', '!'): 0x2191, + ('-', '>'): 0x2192, + ('-', 'v'): 0x2193, + ('<', '>'): 0x2194, + ('U', 'D'): 0x2195, + ('<', '='): 0x21d0, + ('=', '>'): 0x21d2, + ('=', '='): 0x21d4, + ('F', 'A'): 0x2200, + ('d', 'P'): 0x2202, + ('T', 'E'): 0x2203, + ('/', '0'): 0x2205, + ('D', 'E'): 0x2206, + ('N', 'B'): 0x2207, + ('(', '-'): 0x2208, + ('-', ')'): 0x220b, + ('*', 'P'): 0x220f, + ('+', 'Z'): 0x2211, + ('-', '2'): 0x2212, + ('-', '+'): 0x2213, + ('*', '-'): 0x2217, + ('O', 'b'): 0x2218, + ('S', 'b'): 0x2219, + ('R', 'T'): 0x221a, + ('0', '('): 0x221d, + ('0', '0'): 0x221e, + ('-', 'L'): 0x221f, + ('-', 'V'): 0x2220, + ('P', 'P'): 0x2225, + ('A', 'N'): 0x2227, + ('O', 'R'): 0x2228, + ('(', 'U'): 0x2229, + (')', 'U'): 0x222a, + ('I', 'n'): 0x222b, + ('D', 'I'): 0x222c, + ('I', 'o'): 0x222e, + ('.', ':'): 0x2234, + (':', '.'): 0x2235, + (':', 'R'): 0x2236, + (':', ':'): 0x2237, + ('?', '1'): 0x223c, + ('C', 'G'): 0x223e, + ('?', '-'): 0x2243, + ('?', '='): 0x2245, + ('?', '2'): 0x2248, + ('=', '?'): 0x224c, + ('H', 'I'): 0x2253, + ('!', '='): 0x2260, + ('=', '3'): 0x2261, + ('=', '<'): 0x2264, + ('>', '='): 0x2265, + ('<', '*'): 0x226a, + ('*', '>'): 0x226b, + ('!', '<'): 0x226e, + ('!', '>'): 0x226f, + ('(', 'C'): 0x2282, + (')', 'C'): 0x2283, + ('(', '_'): 0x2286, + (')', '_'): 0x2287, + ('0', '.'): 0x2299, + ('0', '2'): 0x229a, + ('-', 'T'): 0x22a5, + ('.', 'P'): 0x22c5, + (':', '3'): 0x22ee, + ('.', '3'): 0x22ef, + ('E', 'h'): 0x2302, + ('<', '7'): 0x2308, + ('>', '7'): 0x2309, + ('7', '<'): 0x230a, + ('7', '>'): 0x230b, + ('N', 'I'): 0x2310, + ('(', 'A'): 0x2312, + ('T', 'R'): 0x2315, + ('I', 'u'): 0x2320, + ('I', 'l'): 0x2321, + ('<', '/'): 0x2329, + ('/', '>'): 0x232a, + ('V', 's'): 0x2423, + ('1', 'h'): 0x2440, + ('3', 'h'): 0x2441, + ('2', 'h'): 0x2442, + ('4', 'h'): 0x2443, + ('1', 'j'): 0x2446, + ('2', 'j'): 0x2447, + ('3', 'j'): 0x2448, + ('4', 'j'): 0x2449, + ('1', '.'): 0x2488, + ('2', '.'): 0x2489, + ('3', '.'): 0x248a, + ('4', '.'): 0x248b, + ('5', '.'): 0x248c, + ('6', '.'): 0x248d, + ('7', '.'): 0x248e, + ('8', '.'): 0x248f, + ('9', '.'): 0x2490, + ('h', 'h'): 0x2500, + ('H', 'H'): 0x2501, + ('v', 'v'): 0x2502, + ('V', 'V'): 0x2503, + ('3', '-'): 0x2504, + ('3', '_'): 0x2505, + ('3', '!'): 0x2506, + ('3', '/'): 0x2507, + ('4', '-'): 0x2508, + ('4', '_'): 0x2509, + ('4', '!'): 0x250a, + ('4', '/'): 0x250b, + ('d', 'r'): 0x250c, + ('d', 'R'): 0x250d, + ('D', 'r'): 0x250e, + ('D', 'R'): 0x250f, + ('d', 'l'): 0x2510, + ('d', 'L'): 0x2511, + ('D', 'l'): 0x2512, + ('L', 'D'): 0x2513, + ('u', 'r'): 0x2514, + ('u', 'R'): 0x2515, + ('U', 'r'): 0x2516, + ('U', 'R'): 0x2517, + ('u', 'l'): 0x2518, + ('u', 'L'): 0x2519, + ('U', 'l'): 0x251a, + ('U', 'L'): 0x251b, + ('v', 'r'): 0x251c, + ('v', 'R'): 0x251d, + ('V', 'r'): 0x2520, + ('V', 'R'): 0x2523, + ('v', 'l'): 0x2524, + ('v', 'L'): 0x2525, + ('V', 'l'): 0x2528, + ('V', 'L'): 0x252b, + ('d', 'h'): 0x252c, + ('d', 'H'): 0x252f, + ('D', 'h'): 0x2530, + ('D', 'H'): 0x2533, + ('u', 'h'): 0x2534, + ('u', 'H'): 0x2537, + ('U', 'h'): 0x2538, + ('U', 'H'): 0x253b, + ('v', 'h'): 0x253c, + ('v', 'H'): 0x253f, + ('V', 'h'): 0x2542, + ('V', 'H'): 0x254b, + ('F', 'D'): 0x2571, + ('B', 'D'): 0x2572, + ('T', 'B'): 0x2580, + ('L', 'B'): 0x2584, + ('F', 'B'): 0x2588, + ('l', 'B'): 0x258c, + ('R', 'B'): 0x2590, + ('.', 'S'): 0x2591, + (':', 'S'): 0x2592, + ('?', 'S'): 0x2593, + ('f', 'S'): 0x25a0, + ('O', 'S'): 0x25a1, + ('R', 'O'): 0x25a2, + ('R', 'r'): 0x25a3, + ('R', 'F'): 0x25a4, + ('R', 'Y'): 0x25a5, + ('R', 'H'): 0x25a6, + ('R', 'Z'): 0x25a7, + ('R', 'K'): 0x25a8, + ('R', 'X'): 0x25a9, + ('s', 'B'): 0x25aa, + ('S', 'R'): 0x25ac, + ('O', 'r'): 0x25ad, + ('U', 'T'): 0x25b2, + ('u', 'T'): 0x25b3, + ('P', 'R'): 0x25b6, + ('T', 'r'): 0x25b7, + ('D', 't'): 0x25bc, + ('d', 'T'): 0x25bd, + ('P', 'L'): 0x25c0, + ('T', 'l'): 0x25c1, + ('D', 'b'): 0x25c6, + ('D', 'w'): 0x25c7, + ('L', 'Z'): 0x25ca, + ('0', 'm'): 0x25cb, + ('0', 'o'): 0x25ce, + ('0', 'M'): 0x25cf, + ('0', 'L'): 0x25d0, + ('0', 'R'): 0x25d1, + ('S', 'n'): 0x25d8, + ('I', 'c'): 0x25d9, + ('F', 'd'): 0x25e2, + ('B', 'd'): 0x25e3, + ('*', '2'): 0x2605, + ('*', '1'): 0x2606, + ('<', 'H'): 0x261c, + ('>', 'H'): 0x261e, + ('0', 'u'): 0x263a, + ('0', 'U'): 0x263b, + ('S', 'U'): 0x263c, + ('F', 'm'): 0x2640, + ('M', 'l'): 0x2642, + ('c', 'S'): 0x2660, + ('c', 'H'): 0x2661, + ('c', 'D'): 0x2662, + ('c', 'C'): 0x2663, + ('M', 'd'): 0x2669, + ('M', '8'): 0x266a, + ('M', '2'): 0x266b, + ('M', 'b'): 0x266d, + ('M', 'x'): 0x266e, + ('M', 'X'): 0x266f, + ('O', 'K'): 0x2713, + ('X', 'X'): 0x2717, + ('-', 'X'): 0x2720, + ('I', 'S'): 0x3000, + (',', '_'): 0x3001, + ('.', '_'): 0x3002, + ('+', '"'): 0x3003, + ('+', '_'): 0x3004, + ('*', '_'): 0x3005, + (';', '_'): 0x3006, + ('0', '_'): 0x3007, + ('<', '+'): 0x300a, + ('>', '+'): 0x300b, + ('<', '\''): 0x300c, + ('>', '\''): 0x300d, + ('<', '"'): 0x300e, + ('>', '"'): 0x300f, + ('(', '"'): 0x3010, + (')', '"'): 0x3011, + ('=', 'T'): 0x3012, + ('=', '_'): 0x3013, + ('(', '\''): 0x3014, + (')', '\''): 0x3015, + ('(', 'I'): 0x3016, + (')', 'I'): 0x3017, + ('-', '?'): 0x301c, + ('A', '5'): 0x3041, + ('a', '5'): 0x3042, + ('I', '5'): 0x3043, + ('i', '5'): 0x3044, + ('U', '5'): 0x3045, + ('u', '5'): 0x3046, + ('E', '5'): 0x3047, + ('e', '5'): 0x3048, + ('O', '5'): 0x3049, + ('o', '5'): 0x304a, + ('k', 'a'): 0x304b, + ('g', 'a'): 0x304c, + ('k', 'i'): 0x304d, + ('g', 'i'): 0x304e, + ('k', 'u'): 0x304f, + ('g', 'u'): 0x3050, + ('k', 'e'): 0x3051, + ('g', 'e'): 0x3052, + ('k', 'o'): 0x3053, + ('g', 'o'): 0x3054, + ('s', 'a'): 0x3055, + ('z', 'a'): 0x3056, + ('s', 'i'): 0x3057, + ('z', 'i'): 0x3058, + ('s', 'u'): 0x3059, + ('z', 'u'): 0x305a, + ('s', 'e'): 0x305b, + ('z', 'e'): 0x305c, + ('s', 'o'): 0x305d, + ('z', 'o'): 0x305e, + ('t', 'a'): 0x305f, + ('d', 'a'): 0x3060, + ('t', 'i'): 0x3061, + ('d', 'i'): 0x3062, + ('t', 'U'): 0x3063, + ('t', 'u'): 0x3064, + ('d', 'u'): 0x3065, + ('t', 'e'): 0x3066, + ('d', 'e'): 0x3067, + ('t', 'o'): 0x3068, + ('d', 'o'): 0x3069, + ('n', 'a'): 0x306a, + ('n', 'i'): 0x306b, + ('n', 'u'): 0x306c, + ('n', 'e'): 0x306d, + ('n', 'o'): 0x306e, + ('h', 'a'): 0x306f, + ('b', 'a'): 0x3070, + ('p', 'a'): 0x3071, + ('h', 'i'): 0x3072, + ('b', 'i'): 0x3073, + ('p', 'i'): 0x3074, + ('h', 'u'): 0x3075, + ('b', 'u'): 0x3076, + ('p', 'u'): 0x3077, + ('h', 'e'): 0x3078, + ('b', 'e'): 0x3079, + ('p', 'e'): 0x307a, + ('h', 'o'): 0x307b, + ('b', 'o'): 0x307c, + ('p', 'o'): 0x307d, + ('m', 'a'): 0x307e, + ('m', 'i'): 0x307f, + ('m', 'u'): 0x3080, + ('m', 'e'): 0x3081, + ('m', 'o'): 0x3082, + ('y', 'A'): 0x3083, + ('y', 'a'): 0x3084, + ('y', 'U'): 0x3085, + ('y', 'u'): 0x3086, + ('y', 'O'): 0x3087, + ('y', 'o'): 0x3088, + ('r', 'a'): 0x3089, + ('r', 'i'): 0x308a, + ('r', 'u'): 0x308b, + ('r', 'e'): 0x308c, + ('r', 'o'): 0x308d, + ('w', 'A'): 0x308e, + ('w', 'a'): 0x308f, + ('w', 'i'): 0x3090, + ('w', 'e'): 0x3091, + ('w', 'o'): 0x3092, + ('n', '5'): 0x3093, + ('v', 'u'): 0x3094, + ('"', '5'): 0x309b, + ('0', '5'): 0x309c, + ('*', '5'): 0x309d, + ('+', '5'): 0x309e, + ('a', '6'): 0x30a1, + ('A', '6'): 0x30a2, + ('i', '6'): 0x30a3, + ('I', '6'): 0x30a4, + ('u', '6'): 0x30a5, + ('U', '6'): 0x30a6, + ('e', '6'): 0x30a7, + ('E', '6'): 0x30a8, + ('o', '6'): 0x30a9, + ('O', '6'): 0x30aa, + ('K', 'a'): 0x30ab, + ('G', 'a'): 0x30ac, + ('K', 'i'): 0x30ad, + ('G', 'i'): 0x30ae, + ('K', 'u'): 0x30af, + ('G', 'u'): 0x30b0, + ('K', 'e'): 0x30b1, + ('G', 'e'): 0x30b2, + ('K', 'o'): 0x30b3, + ('G', 'o'): 0x30b4, + ('S', 'a'): 0x30b5, + ('Z', 'a'): 0x30b6, + ('S', 'i'): 0x30b7, + ('Z', 'i'): 0x30b8, + ('S', 'u'): 0x30b9, + ('Z', 'u'): 0x30ba, + ('S', 'e'): 0x30bb, + ('Z', 'e'): 0x30bc, + ('S', 'o'): 0x30bd, + ('Z', 'o'): 0x30be, + ('T', 'a'): 0x30bf, + ('D', 'a'): 0x30c0, + ('T', 'i'): 0x30c1, + ('D', 'i'): 0x30c2, + ('T', 'U'): 0x30c3, + ('T', 'u'): 0x30c4, + ('D', 'u'): 0x30c5, + ('T', 'e'): 0x30c6, + ('D', 'e'): 0x30c7, + ('T', 'o'): 0x30c8, + ('D', 'o'): 0x30c9, + ('N', 'a'): 0x30ca, + ('N', 'i'): 0x30cb, + ('N', 'u'): 0x30cc, + ('N', 'e'): 0x30cd, + ('N', 'o'): 0x30ce, + ('H', 'a'): 0x30cf, + ('B', 'a'): 0x30d0, + ('P', 'a'): 0x30d1, + ('H', 'i'): 0x30d2, + ('B', 'i'): 0x30d3, + ('P', 'i'): 0x30d4, + ('H', 'u'): 0x30d5, + ('B', 'u'): 0x30d6, + ('P', 'u'): 0x30d7, + ('H', 'e'): 0x30d8, + ('B', 'e'): 0x30d9, + ('P', 'e'): 0x30da, + ('H', 'o'): 0x30db, + ('B', 'o'): 0x30dc, + ('P', 'o'): 0x30dd, + ('M', 'a'): 0x30de, + ('M', 'i'): 0x30df, + ('M', 'u'): 0x30e0, + ('M', 'e'): 0x30e1, + ('M', 'o'): 0x30e2, + ('Y', 'A'): 0x30e3, + ('Y', 'a'): 0x30e4, + ('Y', 'U'): 0x30e5, + ('Y', 'u'): 0x30e6, + ('Y', 'O'): 0x30e7, + ('Y', 'o'): 0x30e8, + ('R', 'a'): 0x30e9, + ('R', 'i'): 0x30ea, + ('R', 'u'): 0x30eb, + ('R', 'e'): 0x30ec, + ('R', 'o'): 0x30ed, + ('W', 'A'): 0x30ee, + ('W', 'a'): 0x30ef, + ('W', 'i'): 0x30f0, + ('W', 'e'): 0x30f1, + ('W', 'o'): 0x30f2, + ('N', '6'): 0x30f3, + ('V', 'u'): 0x30f4, + ('K', 'A'): 0x30f5, + ('K', 'E'): 0x30f6, + ('V', 'a'): 0x30f7, + ('V', 'i'): 0x30f8, + ('V', 'e'): 0x30f9, + ('V', 'o'): 0x30fa, + ('.', '6'): 0x30fb, + ('-', '6'): 0x30fc, + ('*', '6'): 0x30fd, + ('+', '6'): 0x30fe, + ('b', '4'): 0x3105, + ('p', '4'): 0x3106, + ('m', '4'): 0x3107, + ('f', '4'): 0x3108, + ('d', '4'): 0x3109, + ('t', '4'): 0x310a, + ('n', '4'): 0x310b, + ('l', '4'): 0x310c, + ('g', '4'): 0x310d, + ('k', '4'): 0x310e, + ('h', '4'): 0x310f, + ('j', '4'): 0x3110, + ('q', '4'): 0x3111, + ('x', '4'): 0x3112, + ('z', 'h'): 0x3113, + ('c', 'h'): 0x3114, + ('s', 'h'): 0x3115, + ('r', '4'): 0x3116, + ('z', '4'): 0x3117, + ('c', '4'): 0x3118, + ('s', '4'): 0x3119, + ('a', '4'): 0x311a, + ('o', '4'): 0x311b, + ('e', '4'): 0x311c, + ('a', 'i'): 0x311e, + ('e', 'i'): 0x311f, + ('a', 'u'): 0x3120, + ('o', 'u'): 0x3121, + ('a', 'n'): 0x3122, + ('e', 'n'): 0x3123, + ('a', 'N'): 0x3124, + ('e', 'N'): 0x3125, + ('e', 'r'): 0x3126, + ('i', '4'): 0x3127, + ('u', '4'): 0x3128, + ('i', 'u'): 0x3129, + ('v', '4'): 0x312a, + ('n', 'G'): 0x312b, + ('g', 'n'): 0x312c, + ('1', 'c'): 0x3220, + ('2', 'c'): 0x3221, + ('3', 'c'): 0x3222, + ('4', 'c'): 0x3223, + ('5', 'c'): 0x3224, + ('6', 'c'): 0x3225, + ('7', 'c'): 0x3226, + ('8', 'c'): 0x3227, + ('9', 'c'): 0x3228, + + # code points 0xe000 - 0xefff excluded, they have no assigned + # characters, only used in proposals. + ('f', 'f'): 0xfb00, + ('f', 'i'): 0xfb01, + ('f', 'l'): 0xfb02, + ('f', 't'): 0xfb05, + ('s', 't'): 0xfb06, + + # Vim 5.x compatible digraphs that don't conflict with the above + ('~', '!'): 161, + ('c', '|'): 162, + ('$', '$'): 163, + ('o', 'x'): 164, # currency symbol in ISO 8859-1 + ('Y', '-'): 165, + ('|', '|'): 166, + ('c', 'O'): 169, + ('-', ','): 172, + ('-', '='): 175, + ('~', 'o'): 176, + ('2', '2'): 178, + ('3', '3'): 179, + ('p', 'p'): 182, + ('~', '.'): 183, + ('1', '1'): 185, + ('~', '?'): 191, + ('A', '`'): 192, + ('A', '^'): 194, + ('A', '~'): 195, + ('A', '"'): 196, + ('A', '@'): 197, + ('E', '`'): 200, + ('E', '^'): 202, + ('E', '"'): 203, + ('I', '`'): 204, + ('I', '^'): 206, + ('I', '"'): 207, + ('N', '~'): 209, + ('O', '`'): 210, + ('O', '^'): 212, + ('O', '~'): 213, + ('/', '\\'): 215, # multiplication symbol in ISO 8859-1 + ('U', '`'): 217, + ('U', '^'): 219, + ('I', 'p'): 222, + ('a', '`'): 224, + ('a', '^'): 226, + ('a', '~'): 227, + ('a', '"'): 228, + ('a', '@'): 229, + ('e', '`'): 232, + ('e', '^'): 234, + ('e', '"'): 235, + ('i', '`'): 236, + ('i', '^'): 238, + ('n', '~'): 241, + ('o', '`'): 242, + ('o', '^'): 244, + ('o', '~'): 245, + ('u', '`'): 249, + ('u', '^'): 251, + ('y', '"'): 255, +} diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/input_processor.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/input_processor.py new file mode 100644 index 00000000000..51a3110827a --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/input_processor.py @@ -0,0 +1,372 @@ +# *** encoding: utf-8 *** +""" +An :class:`~.InputProcessor` receives callbacks for the keystrokes parsed from +the input in the :class:`~prompt_toolkit.inputstream.InputStream` instance. + +The `InputProcessor` will according to the implemented keybindings call the +correct callbacks when new key presses are feed through `feed`. +""" +from __future__ import unicode_literals +from prompt_toolkit.buffer import EditReadOnlyBuffer +from prompt_toolkit.filters.cli import ViNavigationMode +from prompt_toolkit.keys import Keys, Key +from prompt_toolkit.utils import Event + +from .registry import BaseRegistry + +from collections import deque +from six.moves import range +import weakref +import six + +__all__ = ( + 'InputProcessor', + 'KeyPress', +) + + +class KeyPress(object): + """ + :param key: A `Keys` instance or text (one character). + :param data: The received string on stdin. (Often vt100 escape codes.) + """ + def __init__(self, key, data=None): + assert isinstance(key, (six.text_type, Key)) + assert data is None or isinstance(data, six.text_type) + + if data is None: + data = key.name if isinstance(key, Key) else key + + self.key = key + self.data = data + + def __repr__(self): + return '%s(key=%r, data=%r)' % ( + self.__class__.__name__, self.key, self.data) + + def __eq__(self, other): + return self.key == other.key and self.data == other.data + + +class InputProcessor(object): + """ + Statemachine that receives :class:`KeyPress` instances and according to the + key bindings in the given :class:`Registry`, calls the matching handlers. + + :: + + p = InputProcessor(registry) + + # Send keys into the processor. + p.feed(KeyPress(Keys.ControlX, '\x18')) + p.feed(KeyPress(Keys.ControlC, '\x03') + + # Process all the keys in the queue. + p.process_keys() + + # Now the ControlX-ControlC callback will be called if this sequence is + # registered in the registry. + + :param registry: `BaseRegistry` instance. + :param cli_ref: weakref to `CommandLineInterface`. + """ + def __init__(self, registry, cli_ref): + assert isinstance(registry, BaseRegistry) + + self._registry = registry + self._cli_ref = cli_ref + + self.beforeKeyPress = Event(self) + self.afterKeyPress = Event(self) + + # The queue of keys not yet send to our _process generator/state machine. + self.input_queue = deque() + + # The key buffer that is matched in the generator state machine. + # (This is at at most the amount of keys that make up for one key binding.) + self.key_buffer = [] + + # Simple macro recording. (Like readline does.) + self.record_macro = False + self.macro = [] + + self.reset() + + def reset(self): + self._previous_key_sequence = [] + self._previous_handler = None + + self._process_coroutine = self._process() + self._process_coroutine.send(None) + + #: Readline argument (for repetition of commands.) + #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html + self.arg = None + + def start_macro(self): + " Start recording macro. " + self.record_macro = True + self.macro = [] + + def end_macro(self): + " End recording macro. " + self.record_macro = False + + def call_macro(self): + for k in self.macro: + self.feed(k) + + def _get_matches(self, key_presses): + """ + For a list of :class:`KeyPress` instances. Give the matching handlers + that would handle this. + """ + keys = tuple(k.key for k in key_presses) + cli = self._cli_ref() + + # Try match, with mode flag + return [b for b in self._registry.get_bindings_for_keys(keys) if b.filter(cli)] + + def _is_prefix_of_longer_match(self, key_presses): + """ + For a list of :class:`KeyPress` instances. Return True if there is any + handler that is bound to a suffix of this keys. + """ + keys = tuple(k.key for k in key_presses) + cli = self._cli_ref() + + # Get the filters for all the key bindings that have a longer match. + # Note that we transform it into a `set`, because we don't care about + # the actual bindings and executing it more than once doesn't make + # sense. (Many key bindings share the same filter.) + filters = set(b.filter for b in self._registry.get_bindings_starting_with_keys(keys)) + + # When any key binding is active, return True. + return any(f(cli) for f in filters) + + def _process(self): + """ + Coroutine implementing the key match algorithm. Key strokes are sent + into this generator, and it calls the appropriate handlers. + """ + buffer = self.key_buffer + retry = False + + while True: + if retry: + retry = False + else: + buffer.append((yield)) + + # If we have some key presses, check for matches. + if buffer: + is_prefix_of_longer_match = self._is_prefix_of_longer_match(buffer) + matches = self._get_matches(buffer) + + # When eager matches were found, give priority to them and also + # ignore all the longer matches. + eager_matches = [m for m in matches if m.eager(self._cli_ref())] + + if eager_matches: + matches = eager_matches + is_prefix_of_longer_match = False + + # Exact matches found, call handler. + if not is_prefix_of_longer_match and matches: + self._call_handler(matches[-1], key_sequence=buffer[:]) + del buffer[:] # Keep reference. + + # No match found. + elif not is_prefix_of_longer_match and not matches: + retry = True + found = False + + # Loop over the input, try longest match first and shift. + for i in range(len(buffer), 0, -1): + matches = self._get_matches(buffer[:i]) + if matches: + self._call_handler(matches[-1], key_sequence=buffer[:i]) + del buffer[:i] + found = True + break + + if not found: + del buffer[:1] + + def feed(self, key_press): + """ + Add a new :class:`KeyPress` to the input queue. + (Don't forget to call `process_keys` in order to process the queue.) + """ + assert isinstance(key_press, KeyPress) + self.input_queue.append(key_press) + + def process_keys(self): + """ + Process all the keys in the `input_queue`. + (To be called after `feed`.) + + Note: because of the `feed`/`process_keys` separation, it is + possible to call `feed` from inside a key binding. + This function keeps looping until the queue is empty. + """ + while self.input_queue: + key_press = self.input_queue.popleft() + + if key_press.key != Keys.CPRResponse: + self.beforeKeyPress.fire() + + self._process_coroutine.send(key_press) + + if key_press.key != Keys.CPRResponse: + self.afterKeyPress.fire() + + # Invalidate user interface. + cli = self._cli_ref() + if cli: + cli.invalidate() + + def _call_handler(self, handler, key_sequence=None): + was_recording = self.record_macro + arg = self.arg + self.arg = None + + event = KeyPressEvent( + weakref.ref(self), arg=arg, key_sequence=key_sequence, + previous_key_sequence=self._previous_key_sequence, + is_repeat=(handler == self._previous_handler)) + + # Save the state of the current buffer. + cli = event.cli # Can be `None` (In unit-tests only.) + + if handler.save_before(event) and cli: + cli.current_buffer.save_to_undo_stack() + + # Call handler. + try: + handler.call(event) + self._fix_vi_cursor_position(event) + + except EditReadOnlyBuffer: + # When a key binding does an attempt to change a buffer which is + # read-only, we can just silently ignore that. + pass + + self._previous_key_sequence = key_sequence + self._previous_handler = handler + + # Record the key sequence in our macro. (Only if we're in macro mode + # before and after executing the key.) + if self.record_macro and was_recording: + self.macro.extend(key_sequence) + + def _fix_vi_cursor_position(self, event): + """ + After every command, make sure that if we are in Vi navigation mode, we + never put the cursor after the last character of a line. (Unless it's + an empty line.) + """ + cli = self._cli_ref() + if cli: + buff = cli.current_buffer + preferred_column = buff.preferred_column + + if (ViNavigationMode()(event.cli) and + buff.document.is_cursor_at_the_end_of_line and + len(buff.document.current_line) > 0): + buff.cursor_position -= 1 + + # Set the preferred_column for arrow up/down again. + # (This was cleared after changing the cursor position.) + buff.preferred_column = preferred_column + + + +class KeyPressEvent(object): + """ + Key press event, delivered to key bindings. + + :param input_processor_ref: Weak reference to the `InputProcessor`. + :param arg: Repetition argument. + :param key_sequence: List of `KeyPress` instances. + :param previouskey_sequence: Previous list of `KeyPress` instances. + :param is_repeat: True when the previous event was delivered to the same handler. + """ + def __init__(self, input_processor_ref, arg=None, key_sequence=None, + previous_key_sequence=None, is_repeat=False): + self._input_processor_ref = input_processor_ref + self.key_sequence = key_sequence + self.previous_key_sequence = previous_key_sequence + + #: True when the previous key sequence was handled by the same handler. + self.is_repeat = is_repeat + + self._arg = arg + + def __repr__(self): + return 'KeyPressEvent(arg=%r, key_sequence=%r, is_repeat=%r)' % ( + self.arg, self.key_sequence, self.is_repeat) + + @property + def data(self): + return self.key_sequence[-1].data + + @property + def input_processor(self): + return self._input_processor_ref() + + @property + def cli(self): + """ + Command line interface. + """ + return self.input_processor._cli_ref() + + @property + def current_buffer(self): + """ + The current buffer. + """ + return self.cli.current_buffer + + @property + def arg(self): + """ + Repetition argument. + """ + if self._arg == '-': + return -1 + + result = int(self._arg or 1) + + # Don't exceed a million. + if int(result) >= 1000000: + result = 1 + + return result + + @property + def arg_present(self): + """ + True if repetition argument was explicitly provided. + """ + return self._arg is not None + + def append_to_arg_count(self, data): + """ + Add digit to the input argument. + + :param data: the typed digit as string + """ + assert data in '-0123456789' + current = self._arg + + if data == '-': + assert current is None or current == '-' + result = data + elif current is None: + result = data + else: + result = "%s%s" % (current, data) + + self.input_processor.arg = result diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/manager.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/manager.py new file mode 100644 index 00000000000..83612c2a5cc --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/manager.py @@ -0,0 +1,96 @@ +""" +DEPRECATED: +Use `prompt_toolkit.key_binding.defaults.load_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. + +You don't have to use this, but it's practical. + +Usage:: + + manager = KeyBindingManager() + app = Application(key_bindings_registry=manager.registry) +""" +from __future__ import unicode_literals +from .defaults import load_key_bindings +from prompt_toolkit.filters import to_cli_filter +from prompt_toolkit.key_binding.registry import Registry, ConditionalRegistry, MergedRegistry + +__all__ = ( + 'KeyBindingManager', +) + + +class KeyBindingManager(object): + """ + Utility for loading all key bindings into memory. + + :param registry: Optional `Registry` instance. + :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. + + :param enable_vi_mode: Deprecated! + """ + 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): + + assert registry is None or isinstance(registry, Registry) + assert get_search_state is None or callable(get_search_state) + enable_all = to_cli_filter(enable_all) + + defaults = load_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): + """ + Create a ``KeyBindingManager`` with the defaults 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 cls(**kw) + + def reset(self, cli): + # For backwards compatibility. + pass + + def get_vi_state(self, cli): + # Deprecated! + return cli.vi_state diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/registry.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/registry.py new file mode 100644 index 00000000000..24d0e729a1b --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/registry.py @@ -0,0 +1,350 @@ +""" +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, with_metaclass + +__all__ = ( + 'BaseRegistry', + 'Registry', + 'ConditionalRegistry', + 'MergedRegistry', +) + + +class _Binding(object): + """ + (Immutable binding class.) + """ + def __init__(self, keys, handler, filter=None, eager=None, save_before=None): + assert isinstance(keys, tuple) + assert callable(handler) + assert isinstance(filter, CLIFilter) + assert isinstance(eager, CLIFilter) + assert callable(save_before) + + self.keys = keys + self.handler = handler + self.filter = filter + self.eager = eager + self.save_before = save_before + + def call(self, event): + return self.handler(event) + + def __repr__(self): + return '%s(keys=%r, handler=%r)' % ( + self.__class__.__name__, self.keys, self.handler) + + +class BaseRegistry(with_metaclass(ABCMeta, object)): + """ + Interface for a Registry. + """ + _version = 0 # For cache invalidation. + + @abstractmethod + def get_bindings_for_keys(self, keys): + pass + + @abstractmethod + def get_bindings_starting_with_keys(self, keys): + pass + + # `add_binding` and `remove_binding` don't have to be part of this + # interface. + + +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() + + def add_binding(self, *keys, **kwargs): + """ + Decorator for annotating key bindings. + + :param filter: :class:`~prompt_toolkit.filters.CLIFilter` to determine + when this key binding is active. + :param eager: :class:`~prompt_toolkit.filters.CLIFilter` or `bool`. + When True, ignore potential longer matches when this key binding is + hit. E.g. when there is an active eager key binding for Ctrl-X, + execute the handler immediately and ignore the key binding for + Ctrl-X Ctrl-E of which it is a prefix. + :param save_before: Callable that takes an `Event` and returns True if + we should save the current buffer, before handling the event. + (That's the default.) + """ + filter = to_cli_filter(kwargs.pop('filter', True)) + eager = to_cli_filter(kwargs.pop('eager', False)) + save_before = kwargs.pop('save_before', lambda e: True) + to_cli_filter(kwargs.pop('invalidate_ui', True)) # Deprecated! (ignored.) + + assert not kwargs + assert keys + assert all(isinstance(k, (Key, text_type)) for k in keys), \ + 'Key bindings should consist of Key and string (unicode) instances.' + assert callable(save_before) + + if isinstance(filter, Never): + # When a filter is Never, it will always stay disabled, so in that case + # don't bother putting it in the registry. It will slow down every key + # press otherwise. + def decorator(func): + return func + else: + def decorator(func): + self.key_bindings.append( + _Binding(keys, func, filter=filter, eager=eager, + save_before=save_before)) + self._clear_cache() + + return func + return decorator + + def remove_binding(self, function): + """ + Remove a key binding. + + This expects a function that was given to `add_binding` method as + parameter. Raises `ValueError` when the given function was not + registered before. + """ + assert callable(function) + + for b in self.key_bindings: + if b.handler == function: + self.key_bindings.remove(b) + self._clear_cache() + return + + # No key binding found for this function. Raise ValueError. + raise ValueError('Binding not found: %r' % (function, )) + + def get_bindings_for_keys(self, keys): + """ + Return a list of key bindings that can handle this key. + (This return also inactive bindings, so the `filter` still has to be + called, for checking it.) + + :param keys: tuple of keys. + """ + def get(): + result = [] + for b in self.key_bindings: + if len(keys) == len(b.keys): + match = True + any_count = 0 + + for i, j in zip(b.keys, keys): + if i != j and i != Keys.Any: + match = False + break + + if i == Keys.Any: + any_count += 1 + + if match: + result.append((any_count, b)) + + # Place bindings that have more 'Any' occurences in them at the end. + result = sorted(result, key=lambda item: -item[0]) + + return [item[1] for item in result] + + return self._get_bindings_for_keys_cache.get(keys, get) + + def get_bindings_starting_with_keys(self, keys): + """ + Return a list of key bindings that handle a key sequence starting with + `keys`. (It does only return bindings for which the sequences are + longer than `keys`. And like `get_bindings_for_keys`, it also includes + inactive bindings.) + + :param keys: tuple of keys. + """ + def get(): + result = [] + for b in self.key_bindings: + if len(keys) < len(b.keys): + match = True + for i, j in zip(b.keys, keys): + if i != j and i != Keys.Any: + match = False + break + if match: + result.append(b) + 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): + return True # or False + + registy = ConditionalRegistry(registry, setting_is_true) + + When new key bindings are added to this object. They are also + enable/disabled according to the given `filter`. + + :param registries: List of `Registry` objects. + :param filter: `CLIFilter` object. + """ + def __init__(self, registry=None, filter=True): + registry = registry or Registry() + assert isinstance(registry, BaseRegistry) + + _AddRemoveMixin.__init__(self) + + self.registry = registry + self.filter = to_cli_filter(filter) + + def _update_cache(self): + " If the original registry was changed. Update our copy version. " + expected_version = (self.registry._version, self._extra_registry._version) + + if self._last_version != expected_version: + registry2 = Registry() + + # Copy all bindings from `self.registry`, adding our condition. + for reg in (self.registry, self._extra_registry): + for b in reg.key_bindings: + registry2.key_bindings.append( + _Binding( + keys=b.keys, + handler=b.handler, + filter=self.filter & b.filter, + eager=b.eager, + save_before=b.save_before)) + + self._registry2 = registry2 + self._last_version = expected_version + + +class MergedRegistry(_AddRemoveMixin): + """ + Merge multiple registries of key bindings into one. + + This class acts as a proxy to multiple `Registry` objects, but behaves as + if this is just one bigger `Registry`. + + :param registries: List of `Registry` objects. + """ + def __init__(self, registries): + assert all(isinstance(r, BaseRegistry) for r in registries) + + _AddRemoveMixin.__init__(self) + + self.registries = registries + + def _update_cache(self): + """ + If one of the original registries was changed. Update our merged + version. + """ + expected_version = ( + tuple(r._version for r in self.registries) + + (self._extra_registry._version, )) + + if self._last_version != expected_version: + registry2 = Registry() + + for reg in self.registries: + registry2.key_bindings.extend(reg.key_bindings) + + # Copy all bindings from `self._extra_registry`. + registry2.key_bindings.extend(self._extra_registry.key_bindings) + + self._registry2 = registry2 + self._last_version = expected_version diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/vi_state.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/vi_state.py new file mode 100644 index 00000000000..92ce3cbd292 --- /dev/null +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/vi_state.py @@ -0,0 +1,61 @@ +from __future__ import unicode_literals + +__all__ = ( + 'InputMode', + 'CharacterFind', + 'ViState', +) + + +class InputMode(object): + INSERT = 'vi-insert' + INSERT_MULTIPLE = 'vi-insert-multiple' + NAVIGATION = 'vi-navigation' + REPLACE = 'vi-replace' + + +class CharacterFind(object): + def __init__(self, character, backwards=False): + self.character = character + self.backwards = backwards + + +class ViState(object): + """ + Mutable class to hold the state of the Vi navigation. + """ + def __init__(self): + #: None or CharacterFind instance. (This is used to repeat the last + #: search in Vi mode, by pressing the 'n' or 'N' in navigation mode.) + self.last_character_find = None + + # When an operator is given and we are waiting for text object, + # -- e.g. in the case of 'dw', after the 'd' --, an operator callback + # is set here. + self.operator_func = None + self.operator_arg = None + + #: Named registers. Maps register name (e.g. 'a') to + #: :class:`ClipboardData` instances. + self.named_registers = {} + + #: The Vi mode we're currently in to. + self.input_mode = InputMode.INSERT + + #: Waiting for digraph. + self.waiting_for_digraph = False + self.digraph_symbol1 = None # (None or a symbol.) + + #: When true, make ~ act as an operator. + self.tilde_operator = False + + def reset(self, mode=InputMode.INSERT): + """ + Reset state, go back to the given mode. INSERT by default. + """ + # Go back to insert mode. + self.input_mode = mode + + self.waiting_for_digraph = False + self.operator_func = None + self.operator_arg = None |