From 227038a454943a075c51b60988c53f77a605fa10 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 26 Feb 2021 05:10:02 +0100 Subject: Adding upstream version 3.0.16. Signed-off-by: Daniel Baumann --- ptpython/key_bindings.py | 326 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 ptpython/key_bindings.py (limited to 'ptpython/key_bindings.py') diff --git a/ptpython/key_bindings.py b/ptpython/key_bindings.py new file mode 100644 index 0000000..86317f9 --- /dev/null +++ b/ptpython/key_bindings.py @@ -0,0 +1,326 @@ +from prompt_toolkit.application import get_app +from prompt_toolkit.document import Document +from prompt_toolkit.enums import DEFAULT_BUFFER +from prompt_toolkit.filters import ( + Condition, + emacs_insert_mode, + emacs_mode, + has_focus, + has_selection, + vi_insert_mode, +) +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.named_commands import get_by_name +from prompt_toolkit.keys import Keys + +from .utils import document_is_multiline_python + +__all__ = [ + "load_python_bindings", + "load_sidebar_bindings", + "load_confirm_exit_bindings", +] + + +@Condition +def tab_should_insert_whitespace(): + """ + When the 'tab' key is pressed with only whitespace character before the + cursor, do autocompletion. Otherwise, insert indentation. + + Except for the first character at the first line. Then always do a + completion. It doesn't make sense to start the first line with + indentation. + """ + b = get_app().current_buffer + before_cursor = b.document.current_line_before_cursor + + return bool(b.text and (not before_cursor or before_cursor.isspace())) + + +def load_python_bindings(python_input): + """ + Custom key bindings. + """ + bindings = KeyBindings() + + sidebar_visible = Condition(lambda: python_input.show_sidebar) + handle = bindings.add + + @handle("c-l") + def _(event): + """ + Clear whole screen and render again -- also when the sidebar is visible. + """ + event.app.renderer.clear() + + @handle("c-z") + def _(event): + """ + Suspend. + """ + if python_input.enable_system_bindings: + event.app.suspend_to_background() + + # Delete word before cursor, but use all Python symbols as separators + # (WORD=False). + handle("c-w")(get_by_name("backward-kill-word")) + + @handle("f2") + def _(event): + """ + Show/hide sidebar. + """ + python_input.show_sidebar = not python_input.show_sidebar + if python_input.show_sidebar: + event.app.layout.focus(python_input.ptpython_layout.sidebar) + else: + event.app.layout.focus_last() + + @handle("f3") + def _(event): + """ + Select from the history. + """ + python_input.enter_history() + + @handle("f4") + def _(event): + """ + Toggle between Vi and Emacs mode. + """ + python_input.vi_mode = not python_input.vi_mode + + @handle("f6") + def _(event): + """ + Enable/Disable paste mode. + """ + python_input.paste_mode = not python_input.paste_mode + + @handle( + "tab", filter=~sidebar_visible & ~has_selection & tab_should_insert_whitespace + ) + def _(event): + """ + When tab should insert whitespace, do that instead of completion. + """ + event.app.current_buffer.insert_text(" ") + + @Condition + def is_multiline(): + return document_is_multiline_python(python_input.default_buffer.document) + + @handle( + "enter", + filter=~sidebar_visible + & ~has_selection + & (vi_insert_mode | emacs_insert_mode) + & has_focus(DEFAULT_BUFFER) + & ~is_multiline, + ) + @handle(Keys.Escape, Keys.Enter, filter=~sidebar_visible & emacs_mode) + def _(event): + """ + Accept input (for single line input). + """ + b = event.current_buffer + + if b.validate(): + # When the cursor is at the end, and we have an empty line: + # drop the empty lines, but return the value. + b.document = Document( + text=b.text.rstrip(), cursor_position=len(b.text.rstrip()) + ) + + b.validate_and_handle() + + @handle( + "enter", + filter=~sidebar_visible + & ~has_selection + & (vi_insert_mode | emacs_insert_mode) + & has_focus(DEFAULT_BUFFER) + & is_multiline, + ) + def _(event): + """ + Behaviour of the Enter key. + + Auto indent after newline/Enter. + (When not in Vi navigaton mode, and when multiline is enabled.) + """ + b = event.current_buffer + empty_lines_required = python_input.accept_input_on_enter or 10000 + + def at_the_end(b): + """we consider the cursor at the end when there is no text after + the cursor, or only whitespace.""" + text = b.document.text_after_cursor + return text == "" or (text.isspace() and not "\n" in text) + + if python_input.paste_mode: + # In paste mode, always insert text. + b.insert_text("\n") + + elif at_the_end(b) and b.document.text.replace(" ", "").endswith( + "\n" * (empty_lines_required - 1) + ): + # When the cursor is at the end, and we have an empty line: + # drop the empty lines, but return the value. + if b.validate(): + b.document = Document( + text=b.text.rstrip(), cursor_position=len(b.text.rstrip()) + ) + + b.validate_and_handle() + else: + auto_newline(b) + + @handle( + "c-d", + filter=~sidebar_visible + & has_focus(python_input.default_buffer) + & Condition( + lambda: + # The current buffer is empty. + not get_app().current_buffer.text + ), + ) + def _(event): + """ + Override Control-D exit, to ask for confirmation. + """ + if python_input.confirm_exit: + # Show exit confirmation and focus it (focusing is important for + # making sure the default buffer key bindings are not active). + python_input.show_exit_confirmation = True + python_input.app.layout.focus( + python_input.ptpython_layout.exit_confirmation + ) + else: + event.app.exit(exception=EOFError) + + @handle("c-c", filter=has_focus(python_input.default_buffer)) + def _(event): + " Abort when Control-C has been pressed. " + event.app.exit(exception=KeyboardInterrupt, style="class:aborting") + + return bindings + + +def load_sidebar_bindings(python_input): + """ + Load bindings for the navigation in the sidebar. + """ + bindings = KeyBindings() + + handle = bindings.add + sidebar_visible = Condition(lambda: python_input.show_sidebar) + + @handle("up", filter=sidebar_visible) + @handle("c-p", filter=sidebar_visible) + @handle("k", filter=sidebar_visible) + def _(event): + " Go to previous option. " + python_input.selected_option_index = ( + python_input.selected_option_index - 1 + ) % python_input.option_count + + @handle("down", filter=sidebar_visible) + @handle("c-n", filter=sidebar_visible) + @handle("j", filter=sidebar_visible) + def _(event): + " Go to next option. " + python_input.selected_option_index = ( + python_input.selected_option_index + 1 + ) % python_input.option_count + + @handle("right", filter=sidebar_visible) + @handle("l", filter=sidebar_visible) + @handle(" ", filter=sidebar_visible) + def _(event): + " Select next value for current option. " + option = python_input.selected_option + option.activate_next() + + @handle("left", filter=sidebar_visible) + @handle("h", filter=sidebar_visible) + def _(event): + " Select previous value for current option. " + option = python_input.selected_option + option.activate_previous() + + @handle("c-c", filter=sidebar_visible) + @handle("c-d", filter=sidebar_visible) + @handle("c-d", filter=sidebar_visible) + @handle("enter", filter=sidebar_visible) + @handle("escape", filter=sidebar_visible) + def _(event): + " Hide sidebar. " + python_input.show_sidebar = False + event.app.layout.focus_last() + + return bindings + + +def load_confirm_exit_bindings(python_input): + """ + Handle yes/no key presses when the exit confirmation is shown. + """ + bindings = KeyBindings() + + handle = bindings.add + confirmation_visible = Condition(lambda: python_input.show_exit_confirmation) + + @handle("y", filter=confirmation_visible) + @handle("Y", filter=confirmation_visible) + @handle("enter", filter=confirmation_visible) + @handle("c-d", filter=confirmation_visible) + def _(event): + """ + Really quit. + """ + event.app.exit(exception=EOFError, style="class:exiting") + + @handle(Keys.Any, filter=confirmation_visible) + def _(event): + """ + Cancel exit. + """ + python_input.show_exit_confirmation = False + python_input.app.layout.focus_previous() + + return bindings + + +def auto_newline(buffer): + r""" + Insert \n at the cursor position. Also add necessary padding. + """ + insert_text = buffer.insert_text + + if buffer.document.current_line_after_cursor: + # When we are in the middle of a line. Always insert a newline. + insert_text("\n") + else: + # Go to new line, but also add indentation. + current_line = buffer.document.current_line_before_cursor.rstrip() + insert_text("\n") + + # Unident if the last line ends with 'pass', remove four spaces. + unindent = current_line.rstrip().endswith(" pass") + + # Copy whitespace from current line + current_line2 = current_line[4:] if unindent else current_line + + for c in current_line2: + if c.isspace(): + insert_text(c) + else: + break + + # If the last line ends with a colon, add four extra spaces. + if current_line[-1:] == ":": + for x in range(4): + insert_text(" ") -- cgit v1.2.3