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

prompt_toolkit not responding to Ctrl+D in Python try/except block #1837

Closed betapro closed 6 months ago

betapro commented 6 months ago

I'm currently working on a Python 3.11.5 script on Linux Mint 21.2 that utilizes the _prompttoolkit library (3.0.43) to create a simple command-line interface for updating information about a person. However, I've encountered an issue where the script does not respond to Ctrl+D as expected, but does respond to Ctrl+C.

from prompt_toolkit import prompt
from prompt_toolkit.key_binding import KeyBindings

def main():
    person = {
        'id': 1, 'name': "David", 'age': 24
    }

    kb = KeyBindings()
    user_input = None

    for column_name, column_value in person.items():
        prompt_text = f"Enter person's {column_name.replace('_', ' ').title()}: "
        try:
            user_input = prompt(prompt_text, default=str(column_value), key_bindings=kb).strip()

        except KeyboardInterrupt:
            print("Control-C pressed")

        except EOFError:
            print("Control-D pressed")

        person[column_name] = user_input if user_input else column_value

    print("Updated person:", person)

if __name__ == "__main__":
    main()

When Ctrl+D is pressed, the interrupt signal is not caught by EOFError. Visually, nothing appears to happen at all in the terminal or console window, and the user is able to carry on entering data without interruption.

  1. Has anyone else encountered a similar problem with prompt_toolkit and Ctrl+D?
  2. Are there specific settings or configurations in prompt_toolkit that I might be overlooking?
jonathanslenders commented 6 months ago

Control-d only works if there is no input present in the input field.

Given that a default value was given, the default first needs to be erased using backspaces, and then control-d works.

For different behavior, add this key binding:

@kb.add('c-d')
def quit(event):
    event.app.exit(exception=EOFError)
betapro commented 6 months ago

Hi Jonathan

Many thanks for your quick reply, which is much appreciated.

Using backspaces until the field is empty enables Ctrl+D to work exactly as you stated. However, would it be feasible (from your perspective) to change prompt_toolkit so that Ctrl+D works without having to backspace? If not, that's okay. I'd just like to know.

Anyway, I played around with your provided key binding:

@kb.add('c-d') def quit(event): event.app.exit(exception=EOFError)

and it worked like a charm, allowing except EOFError to catch Ctrl+D without having to backspace at all.

Thanks so much!

Mike

jonathanslenders commented 6 months ago

@betapro,

Good to hear it works! The current default behavior won't be changing. The reason is that ctrl-d has a second meaning, which is "delete the character after the cursor". So, depending on whether or not there is text present, the behavior changes. This is identical to Gnu Readline on Bash.