Skip to content

Commit 61fc0ab

Browse files
authored
Add pass-through filter for shortcuts (#14080)
This is one way to fix #14070. The alternatives are: - try to change upstream prompt-toolkit to fire all matching keybindings, not only the last one [as it does currently](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/07412a37b38dd70a3d63f2966a21f0a645133264/src/prompt_toolkit/key_binding/key_processor.py#L187) - this would be breaking so we would need to wait for a major release if this would be accepted - _unless_ instead of firing all matching keybindings only keybindings which oped in via a flag would be additionally fired (there is a precedent of `eager` flag) - add a tailored function with logic that figures out what to do based on state of application - I am not a fan of this as these are hard to test already.
2 parents 7c2e736 + 704639d commit 61fc0ab

File tree

4 files changed

+43
-3
lines changed

4 files changed

+43
-3
lines changed

IPython/terminal/shortcuts/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ def create_identifier(handler: Callable):
279279
["right"],
280280
"is_cursor_at_the_end_of_line"
281281
" & default_buffer_focused"
282-
" & emacs_like_insert_mode",
282+
" & emacs_like_insert_mode"
283+
" & pass_through",
283284
),
284285
]
285286

IPython/terminal/shortcuts/auto_suggest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from IPython.core.getipython import get_ipython
2121
from IPython.utils.tokenutil import generate_tokens
2222

23+
from .filters import pass_through
24+
2325

2426
def _get_query(document: Document):
2527
return document.lines[document.cursor_position_row]
@@ -267,7 +269,10 @@ def backspace_and_resume_hint(event: KeyPressEvent):
267269

268270
def resume_hinting(event: KeyPressEvent):
269271
"""Resume autosuggestions"""
270-
return _update_hint(event.current_buffer)
272+
pass_through.reply(event)
273+
# Order matters: if update happened first and event reply second, the
274+
# suggestion would be auto-accepted if both actions are bound to same key.
275+
_update_hint(event.current_buffer)
271276

272277

273278
def up_and_update_hint(event: KeyPressEvent):

IPython/terminal/shortcuts/filters.py

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
from prompt_toolkit.application.current import get_app
1515
from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER
16-
from prompt_toolkit.filters import Condition, emacs_insert_mode, has_completions
16+
from prompt_toolkit.key_binding import KeyPressEvent
17+
from prompt_toolkit.filters import Condition, Filter, emacs_insert_mode, has_completions
1718
from prompt_toolkit.filters import has_focus as has_focus_impl
1819
from prompt_toolkit.filters import (
1920
Always,
@@ -175,6 +176,36 @@ def is_windows_os():
175176
return sys.platform == "win32"
176177

177178

179+
class PassThrough(Filter):
180+
"""A filter allowing to implement pass-through behaviour of keybindings.
181+
182+
Prompt toolkit key processor dispatches only one event per binding match,
183+
which means that adding a new shortcut will suppress the old shortcut
184+
if the keybindings are the same (unless one is filtered out).
185+
186+
To stop a shortcut binding from suppressing other shortcuts:
187+
- add the `pass_through` filter to list of filter, and
188+
- call `pass_through.reply(event)` in the shortcut handler.
189+
"""
190+
191+
def __init__(self):
192+
self._is_replying = False
193+
194+
def reply(self, event: KeyPressEvent):
195+
self._is_replying = True
196+
try:
197+
event.key_processor.reset()
198+
event.key_processor.feed_multiple(event.key_sequence)
199+
event.key_processor.process_keys()
200+
finally:
201+
self._is_replying = False
202+
203+
def __call__(self):
204+
return not self._is_replying
205+
206+
207+
pass_through = PassThrough()
208+
178209
# these one is callable and re-used multiple times hence needs to be
179210
# only defined once beforhand so that transforming back to human-readable
180211
# names works well in the documentation.
@@ -248,6 +279,7 @@ def is_windows_os():
248279
"followed_by_single_quote": following_text("^'"),
249280
"navigable_suggestions": navigable_suggestions,
250281
"cursor_in_leading_ws": cursor_in_leading_ws,
282+
"pass_through": pass_through,
251283
}
252284

253285

docs/autogen_shortcuts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ def format_filter(
8888
return result
8989
elif s in ["Never", "Always"]:
9090
return s.lower()
91+
elif s == "PassThrough":
92+
return "pass_through"
9193
else:
9294
raise ValueError(f"Unknown filter type: {filter_}")
9395

0 commit comments

Comments
 (0)