diff --git a/ptpython/key_bindings.py b/ptpython/key_bindings.py index b01762e6..110b5b35 100644 --- a/ptpython/key_bindings.py +++ b/ptpython/key_bindings.py @@ -1,3 +1,5 @@ +from jedi import Interpreter +from prompt_toolkit.completion import CompleteEvent from prompt_toolkit.application import get_app from prompt_toolkit.document import Document from prompt_toolkit.enums import DEFAULT_BUFFER @@ -6,10 +8,13 @@ emacs_insert_mode, emacs_mode, has_focus, + has_completions, + completion_is_selected, has_selection, vi_insert_mode, ) from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.key_processor import KeyPress from prompt_toolkit.keys import Keys from .utils import document_is_multiline_python @@ -201,6 +206,87 @@ def _(event): " Abort when Control-C has been pressed. " event.app.exit(exception=KeyboardInterrupt, style="class:aborting") + def is_callable(text=""): + completions = Interpreter(text, [locals()]).complete() + match = next((i for i in completions if i.name == text), None) + return match.type in ("class", "function") if match else None + + @Condition + def tac(): + return python_input.tab_apply_completion + + insert_mode = vi_insert_mode | emacs_insert_mode + focused_insert = insert_mode & has_focus(DEFAULT_BUFFER) + shown_not_selected = has_completions & ~completion_is_selected + alt_enter = [KeyPress(Keys.Escape), KeyPress(Keys.Enter)] + + # apply selected completion + @handle('c-j', filter=focused_insert & completion_is_selected & tac) + @handle("enter", filter=focused_insert & completion_is_selected & tac) + def _(event): + b = event.current_buffer + text = b.text + completion = b.complete_state.current_completion + if is_callable(completion.text): + b.insert_text("()") + b.cursor_left() + if text == b.text: + event.cli.key_processor.feed_multiple(alt_enter) + + # apply first completion option when completion menu is showing + @handle('c-j', filter=focused_insert & shown_not_selected & tac) + @handle("enter", filter=focused_insert & shown_not_selected & tac) + def _(event): + b = event.current_buffer + text = b.text + b.complete_next() + completion = b.complete_state.current_completion + b.apply_completion(completion) + if is_callable(completion.text): + b.insert_text("()") + b.cursor_left() + if text == b.text: + event.cli.key_processor.feed_multiple(alt_enter) + + # apply completion if there is only one option, otherwise start completion + @handle("tab", filter=focused_insert & ~has_completions & tac) + @handle("c-space", filter=focused_insert & ~has_completions & tac) + def _(event): + b = event.current_buffer + complete_event = CompleteEvent(completion_requested=True) + completions = b.completer.get_completions(b.document, complete_event) + if len(completions) == 1: + completion = completions[0] + b.apply_completion(completion) + if is_callable(completion.text): + b.insert_text("()") + b.cursor_left() + else: + b.start_completion(insert_common_part=True) + + # apply first completion option if completion menu is showing + @handle("tab", filter=focused_insert & shown_not_selected & tac) + @handle("c-space", filter=focused_insert & shown_not_selected & tac) + def _(event): + b = event.current_buffer + b.complete_next() + completion = b.complete_state.current_completion + b.apply_completion(completion) + if is_callable(completion.text): + b.insert_text("()") + b.cursor_left() + + # apply selected completion option + @handle("tab", filter=focused_insert & completion_is_selected & tac) + @handle("c-space", filter=focused_insert & completion_is_selected & tac) + def _(event): + b = event.current_buffer + completion = b.complete_state.current_completion + b.apply_completion(completion) + if is_callable(completion.text): + b.insert_text("()") + b.cursor_left() + return bindings diff --git a/ptpython/python_input.py b/ptpython/python_input.py index c119e391..f7c865a0 100644 --- a/ptpython/python_input.py +++ b/ptpython/python_input.py @@ -234,6 +234,7 @@ def __init__( self.enable_system_bindings: bool = True self.enable_input_validation: bool = True self.enable_auto_suggest: bool = False + self.tab_apply_completion: bool = True self.enable_mouse_support: bool = False self.enable_history_search: bool = False # When True, like readline, going # back in history will filter the