From 4e7b9408a0b35cabbdc973f131257f4e97a3ddcf 2023-03-30 08:29:49 From: Matthias Bussonnier Date: 2023-03-30 08:29:49 Subject: [PATCH] Fix autosuggestions in multi-line mode, vi command mode delay (#13991) Fixes #13970. Relates to https://github.com/ipython/ipython/issues/13443. Documents https://github.com/ipython/ipython/pull/12603 with a new `emacs_like_insert_mode` filter alias. --- diff --git a/IPython/terminal/shortcuts/__init__.py b/IPython/terminal/shortcuts/__init__.py index 98ead18..0ff5113 100644 --- a/IPython/terminal/shortcuts/__init__.py +++ b/IPython/terminal/shortcuts/__init__.py @@ -181,30 +181,45 @@ AUTO_MATCH_BINDINGS = [ ] AUTO_SUGGEST_BINDINGS = [ + # there are two reasons for re-defining bindings defined upstream: + # 1) prompt-toolkit does not execute autosuggestion bindings in vi mode, + # 2) prompt-toolkit checks if we are at the end of text, not end of line + # hence it does not work in multi-line mode of navigable provider Binding( - auto_suggest.accept_in_vi_insert_mode, + auto_suggest.accept_or_jump_to_end, ["end"], - "default_buffer_focused & (ebivim | ~vi_insert_mode)", + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", ), Binding( - auto_suggest.accept_in_vi_insert_mode, + auto_suggest.accept_or_jump_to_end, ["c-e"], - "vi_insert_mode & default_buffer_focused & ebivim", + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", + ), + Binding( + auto_suggest.accept, + ["c-f"], + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", + ), + Binding( + auto_suggest.accept, + ["right"], + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", ), - Binding(auto_suggest.accept, ["c-f"], "vi_insert_mode & default_buffer_focused"), Binding( auto_suggest.accept_word, ["escape", "f"], - "vi_insert_mode & default_buffer_focused & ebivim", + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", ), Binding( auto_suggest.accept_token, ["c-right"], - "has_suggestion & default_buffer_focused", + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", ), Binding( auto_suggest.discard, ["escape"], + # note this one is using `emacs_insert_mode`, not `emacs_like_insert_mode` + # as in `vi_insert_mode` we do not want `escape` to be shadowed (ever). "has_suggestion & default_buffer_focused & emacs_insert_mode", ), Binding( @@ -241,22 +256,23 @@ AUTO_SUGGEST_BINDINGS = [ Binding( auto_suggest.accept_character, ["escape", "right"], - "has_suggestion & default_buffer_focused", + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", ), Binding( auto_suggest.accept_and_move_cursor_left, ["c-left"], - "has_suggestion & default_buffer_focused", + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", ), Binding( auto_suggest.accept_and_keep_cursor, ["c-down"], - "has_suggestion & default_buffer_focused", + "has_suggestion & default_buffer_focused & emacs_like_insert_mode", ), Binding( auto_suggest.backspace_and_resume_hint, ["backspace"], - "has_suggestion & default_buffer_focused", + # no `has_suggestion` here to allow resuming if no suggestion + "default_buffer_focused & emacs_like_insert_mode", ), ] diff --git a/IPython/terminal/shortcuts/auto_suggest.py b/IPython/terminal/shortcuts/auto_suggest.py index 0c193d9..93c1fa4 100644 --- a/IPython/terminal/shortcuts/auto_suggest.py +++ b/IPython/terminal/shortcuts/auto_suggest.py @@ -2,6 +2,7 @@ import re import tokenize from io import StringIO from typing import Callable, List, Optional, Union, Generator, Tuple +import warnings from prompt_toolkit.buffer import Buffer from prompt_toolkit.key_binding import KeyPressEvent @@ -178,9 +179,8 @@ class NavigableAutoSuggestFromHistory(AutoSuggestFromHistory): break -# Needed for to accept autosuggestions in vi insert mode -def accept_in_vi_insert_mode(event: KeyPressEvent): - """Apply autosuggestion if at end of line.""" +def accept_or_jump_to_end(event: KeyPressEvent): + """Apply autosuggestion or jump to end of line.""" buffer = event.current_buffer d = buffer.document after_cursor = d.text[d.cursor_position :] @@ -193,6 +193,15 @@ def accept_in_vi_insert_mode(event: KeyPressEvent): nc.end_of_line(event) +def _deprected_accept_in_vi_insert_mode(event: KeyPressEvent): + """Accept autosuggestion or jump to end of line. + + .. deprecated:: 8.12 + Use `accept_or_jump_to_end` instead. + """ + return accept_or_jump_to_end(event) + + def accept(event: KeyPressEvent): """Accept autosuggestion""" buffer = event.current_buffer @@ -373,3 +382,16 @@ def swap_autosuggestion_down(event: KeyPressEvent): provider=provider, direction_method=provider.down, ) + + +def __getattr__(key): + if key == "accept_in_vi_insert_mode": + warnings.warn( + "`accept_in_vi_insert_mode` is deprecated since IPython 8.12 and " + "renamed to `accept_or_jump_to_end`. Please update your configuration " + "accordingly", + DeprecationWarning, + stacklevel=2, + ) + return _deprected_accept_in_vi_insert_mode + raise AttributeError diff --git a/IPython/terminal/shortcuts/filters.py b/IPython/terminal/shortcuts/filters.py index a4a6213..4627769 100644 --- a/IPython/terminal/shortcuts/filters.py +++ b/IPython/terminal/shortcuts/filters.py @@ -181,10 +181,33 @@ KEYBINDING_FILTERS = { "vi_mode": vi_mode, "vi_insert_mode": vi_insert_mode, "emacs_insert_mode": emacs_insert_mode, + # https://github.com/ipython/ipython/pull/12603 argued for inclusion of + # emacs key bindings with a configurable `emacs_bindings_in_vi_insert_mode` + # toggle; when the toggle is on user can access keybindigns like `ctrl + e` + # in vi insert mode. Because some of the emacs bindings involve `escape` + # followed by another key, e.g. `escape` followed by `f`, prompt-toolkit + # needs to wait to see if there will be another character typed in before + # executing pure `escape` keybinding; in vi insert mode `escape` switches to + # command mode which is common and performance critical action for vi users. + # To avoid the delay users employ a workaround: + # https://github.com/ipython/ipython/issues/13443#issuecomment-1032753703 + # which involves switching `emacs_bindings_in_vi_insert_mode` off. + # + # For the workaround to work: + # 1) end users need to toggle `emacs_bindings_in_vi_insert_mode` off + # 2) all keybindings which would involve `escape` need to respect that + # toggle by including either: + # - `vi_insert_mode & ebivim` for actions which have emacs keybindings + # predefined upstream in prompt-toolkit, or + # - `emacs_like_insert_mode` for actions which do not have existing + # emacs keybindings predefined upstream (or need overriding of the + # upstream bindings to modify behaviour), defined below. + "emacs_like_insert_mode": (vi_insert_mode & ebivim) | emacs_insert_mode, "has_completions": has_completions, "insert_mode": vi_insert_mode | emacs_insert_mode, "default_buffer_focused": default_buffer_focused, "search_buffer_focused": has_focus(SEARCH_BUFFER), + # `ebivim` stands for emacs bindings in vi insert mode "ebivim": ebivim, "supports_suspend": supports_suspend, "is_windows_os": is_windows_os, diff --git a/IPython/terminal/tests/test_shortcuts.py b/IPython/terminal/tests/test_shortcuts.py index 18c9dab..45bb327 100644 --- a/IPython/terminal/tests/test_shortcuts.py +++ b/IPython/terminal/tests/test_shortcuts.py @@ -1,7 +1,7 @@ import pytest from IPython.terminal.shortcuts.auto_suggest import ( accept, - accept_in_vi_insert_mode, + accept_or_jump_to_end, accept_token, accept_character, accept_word, @@ -22,6 +22,13 @@ from prompt_toolkit.auto_suggest import AutoSuggestFromHistory from unittest.mock import patch, Mock +def test_deprected(): + import IPython.terminal.shortcuts.auto_suggest as iptsa + + with pytest.warns(DeprecationWarning, match=r"8\.12.+accept_or_jump_to_end"): + iptsa.accept_in_vi_insert_mode + + def make_event(text, cursor, suggestion): event = Mock() event.current_buffer = Mock() @@ -80,7 +87,7 @@ def test_autosuggest_at_EOL(text, cursor, suggestion, called): event = make_event(text, cursor, suggestion) event.current_buffer.insert_text = Mock() - accept_in_vi_insert_mode(event) + accept_or_jump_to_end(event) if called: event.current_buffer.insert_text.assert_called() else: