prompt-toolkit / python-prompt-toolkit

Library for building powerful interactive command line applications in Python
https://python-prompt-toolkit.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
9.1k stars 717 forks source link

Unhandled exception in event loop in auto_suggest keybindings #976

Open vaaaaanquish opened 4 years ago

vaaaaanquish commented 4 years ago

Hi. Thanks.

What's issue

I got Unhandled exception in event loop error in this keybinding function. https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/key_binding/bindings/auto_suggest.py#L49

Environment

OS X python 3.6.8 prompt-toolkit == 2.0.9 or 3.0.0

How reproduction

using this sample: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/examples/prompts/auto-suggestion.py

Say something: import [[[[escape f]]] path [[[[escape f]]] 

Say something: import path/homebrew/versions/3.6.8/lib/python3.6/site-packages/prompt_toolkit/key_binding/key_processor.py:273: DeprecationWarning: generator 'KeyProcessor._process' raised StopIteration

Unhandled exception in event loop:
  File ".../prompt_toolkit/eventloop/posix.py", line 154, in _run_task
  File ".../prompt_toolkit/eventloop/context.py", line 115, in new_func
  File ".../prompt_toolkit/application/application.py", line 562, in read_from_input
  File ".../prompt_toolkit/key_binding/key_processor.py", line 273, in process_keys
  File ".../prompt_toolkit/key_binding/key_processor.py", line 180, in _process
  File ".../prompt_toolkit/key_binding/key_processor.py", line 323, in _call_handler
  File ".../prompt_toolkit/key_binding/key_bindings.py", line 78, in call
  File ".../prompt_toolkit/key_binding/bindings/auto_suggest.py", line 51, in _

Exception
Press ENTER to continue...

Solution

Because generator raises StopIteration. We use suggestion_available filter, but app.current_buffer.suggestion becomes None or Suggestion() . It can't catch exception. https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/key_binding/bindings/auto_suggest.py#L31

We can use try/except or Suggestion.__len__.

A: try/except

https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/key_binding/bindings/auto_suggest.py#L49

    @handle('escape', 'f', filter=suggestion_available & emacs_mode)
    def _(event: E) -> None:
        b = event.current_buffer
        suggestion = b.suggestion
        if suggestion:
            t = re.split(r'(\S+\s+)', suggestion.text)
-            b.insert_text(next(x for x in t if x))
+            try:
+                b.insert_text(next(x for x in t if x))
+            except StopIteration:
+                pass
+            except:
+                raise

B: Suggestion.__len__

https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/auto_suggest.py#L48

+    def __len__(self) -> int:
+        return len(self.text)

https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/prompt_toolkit/key_binding/bindings/auto_suggest.py#L31

-        return (app.current_buffer.suggestion is not None and
+        return (app.current_buffer.suggestion and
                app.current_buffer.document.is_cursor_at_the_end)

Comment

I think Solution B is better:)

jonathanslenders commented 4 years ago

Hi @vaaaaanquish,

I just read the issue only now. As I suggested in the PR, testing the length in the condition is the right thing to do, because it means that if at some point the condition is not satisfied, the key binding engine can possibly call another handler.

On top of this, I would actually also add the except StopIteration: pass. Just in case we would ever call this key binding handler from somewhere else, with a different condition. (even if it's unlikely.)

We would need two PRs. One against the 2.0 branch and one against the master branch.

vaaaaanquish commented 4 years ago

@jonathanslenders Thanks.

We would need two PRs. One against the 2.0 branch and one against the master branch.

I understood, I'll do it :)