Skip to content

Commit

Permalink
Fix autosuggestions in multi-line mode, vi command mode delay (#13991)
Browse files Browse the repository at this point in the history
Fixes #13970. Relates to
#13443.

Documents #12603 with a new
`emacs_like_insert_mode` filter alias.
  • Loading branch information
Carreau committed Mar 30, 2023
2 parents ad452c1 + eccd2db commit 4e7b940
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 16 deletions.
38 changes: 27 additions & 11 deletions IPython/terminal/shortcuts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,30 +181,45 @@ def create_identifier(handler: Callable):
]

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(
Expand Down Expand Up @@ -241,22 +256,23 @@ def create_identifier(handler: Callable):
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",
),
]

Expand Down
28 changes: 25 additions & 3 deletions IPython/terminal/shortcuts/auto_suggest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
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
Expand Down Expand Up @@ -178,9 +179,8 @@ def down(self, query: str, other_than: str, history: History) -> None:
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 :]
Expand All @@ -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
Expand Down Expand Up @@ -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
23 changes: 23 additions & 0 deletions IPython/terminal/shortcuts/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,33 @@ def is_windows_os():
"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,
Expand Down
11 changes: 9 additions & 2 deletions IPython/terminal/tests/test_shortcuts.py
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -22,6 +22,13 @@
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()
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 4e7b940

Please sign in to comment.