├── .codecov.yml ├── .github └── workflows │ └── test.yaml ├── .gitignore ├── .readthedocs.yml ├── AUTHORS.rst ├── CHANGELOG ├── LICENSE ├── MANIFEST.in ├── PROJECTS.rst ├── README.rst ├── appveyor.yml ├── docs ├── Makefile ├── conf.py ├── images │ ├── auto-suggestion.png │ ├── bottom-toolbar.png │ ├── colored-prompt.png │ ├── colorful-completions.png │ ├── dialogs │ │ ├── button.png │ │ ├── confirm.png │ │ ├── inputbox.png │ │ ├── messagebox.png │ │ └── styled.png │ ├── hello-world-prompt.png │ ├── html-completion.png │ ├── html-input.png │ ├── logo_400px.png │ ├── multiline-input.png │ ├── number-validator.png │ ├── progress-bars │ │ ├── apt-get.png │ │ ├── colored-title-and-label.png │ │ ├── custom-key-bindings.png │ │ ├── simple-progress-bar.png │ │ └── two-tasks.png │ ├── ptpython-2.png │ ├── ptpython-history-help.png │ ├── ptpython-menu.png │ ├── ptpython.png │ ├── pymux.png │ ├── pyvim.png │ ├── repl │ │ ├── sqlite-1.png │ │ ├── sqlite-2.png │ │ ├── sqlite-3.png │ │ ├── sqlite-4.png │ │ ├── sqlite-5.png │ │ └── sqlite-6.png │ └── rprompt.png ├── index.rst ├── make.bat ├── pages │ ├── advanced_topics │ │ ├── architecture.rst │ │ ├── asyncio.rst │ │ ├── filters.rst │ │ ├── index.rst │ │ ├── input_hooks.rst │ │ ├── key_bindings.rst │ │ ├── rendering_flow.rst │ │ ├── rendering_pipeline.rst │ │ ├── styling.rst │ │ └── unit_testing.rst │ ├── asking_for_input.rst │ ├── dialogs.rst │ ├── full_screen_apps.rst │ ├── gallery.rst │ ├── getting_started.rst │ ├── printing_text.rst │ ├── progress_bars.rst │ ├── reference.rst │ ├── related_projects.rst │ ├── tutorials │ │ ├── index.rst │ │ └── repl.rst │ └── upgrading │ │ ├── 2.0.rst │ │ ├── 3.0.rst │ │ └── index.rst └── requirements.txt ├── examples ├── dialogs │ ├── button_dialog.py │ ├── checkbox_dialog.py │ ├── input_dialog.py │ ├── messagebox.py │ ├── password_dialog.py │ ├── progress_dialog.py │ ├── radio_dialog.py │ ├── styled_messagebox.py │ └── yes_no_dialog.py ├── full-screen │ ├── ansi-art-and-textarea.py │ ├── buttons.py │ ├── calculator.py │ ├── dummy-app.py │ ├── full-screen-demo.py │ ├── hello-world.py │ ├── no-layout.py │ ├── pager.py │ ├── scrollable-panes │ │ ├── simple-example.py │ │ └── with-completion-menu.py │ ├── simple-demos │ │ ├── alignment.py │ │ ├── autocompletion.py │ │ ├── colorcolumn.py │ │ ├── cursorcolumn-cursorline.py │ │ ├── float-transparency.py │ │ ├── floats.py │ │ ├── focus.py │ │ ├── horizontal-align.py │ │ ├── horizontal-split.py │ │ ├── line-prefixes.py │ │ ├── margins.py │ │ ├── vertical-align.py │ │ └── vertical-split.py │ ├── split-screen.py │ └── text-editor.py ├── gevent-get-input.py ├── print-text │ ├── ansi-colors.py │ ├── ansi.py │ ├── html.py │ ├── named-colors.py │ ├── print-formatted-text.py │ ├── print-frame.py │ ├── prompt-toolkit-logo-ansi-art.py │ ├── pygments-tokens.py │ └── true-color-demo.py ├── progress-bar │ ├── a-lot-of-parallel-tasks.py │ ├── colored-title-and-label.py │ ├── custom-key-bindings.py │ ├── many-parallel-tasks.py │ ├── nested-progress-bars.py │ ├── scrolling-task-name.py │ ├── simple-progress-bar.py │ ├── styled-1.py │ ├── styled-2.py │ ├── styled-apt-get-install.py │ ├── styled-rainbow.py │ ├── styled-tqdm-1.py │ ├── styled-tqdm-2.py │ ├── two-tasks.py │ └── unknown-length.py ├── prompts │ ├── accept-default.py │ ├── asyncio-prompt.py │ ├── auto-completion │ │ ├── autocomplete-with-control-space.py │ │ ├── autocompletion-like-readline.py │ │ ├── autocompletion.py │ │ ├── colored-completions-with-formatted-text.py │ │ ├── colored-completions.py │ │ ├── combine-multiple-completers.py │ │ ├── fuzzy-custom-completer.py │ │ ├── fuzzy-word-completer.py │ │ ├── multi-column-autocompletion-with-meta.py │ │ ├── multi-column-autocompletion.py │ │ ├── nested-autocompletion.py │ │ └── slow-completions.py │ ├── auto-suggestion.py │ ├── autocorrection.py │ ├── bottom-toolbar.py │ ├── clock-input.py │ ├── colored-prompt.py │ ├── confirmation-prompt.py │ ├── cursor-shapes.py │ ├── custom-key-binding.py │ ├── custom-lexer.py │ ├── custom-vi-operator-and-text-object.py │ ├── enforce-tty-input-output.py │ ├── fancy-zsh-prompt.py │ ├── finalterm-shell-integration.py │ ├── get-input-vi-mode.py │ ├── get-input-with-default.py │ ├── get-input.py │ ├── get-multiline-input.py │ ├── get-password-with-toggle-display-shortcut.py │ ├── get-password.py │ ├── history │ │ ├── persistent-history.py │ │ └── slow-history.py │ ├── html-input.py │ ├── input-validation.py │ ├── inputhook.py │ ├── mouse-support.py │ ├── multiline-autosuggest.py │ ├── multiline-prompt.py │ ├── no-wrapping.py │ ├── operate-and-get-next.py │ ├── patch-stdout.py │ ├── placeholder-text.py │ ├── regular-language.py │ ├── rprompt.py │ ├── swap-light-and-dark-colors.py │ ├── switch-between-vi-emacs.py │ ├── system-clipboard-integration.py │ ├── system-prompt.py │ ├── terminal-title.py │ └── up-arrow-partial-string-matching.py ├── ssh │ └── asyncssh-server.py ├── telnet │ ├── chat-app.py │ ├── dialog.py │ ├── hello-world.py │ └── toolbar.py └── tutorial │ ├── README.md │ └── sqlite-cli.py ├── pyproject.toml ├── src └── prompt_toolkit │ ├── __init__.py │ ├── application │ ├── __init__.py │ ├── application.py │ ├── current.py │ ├── dummy.py │ └── run_in_terminal.py │ ├── auto_suggest.py │ ├── buffer.py │ ├── cache.py │ ├── clipboard │ ├── __init__.py │ ├── base.py │ ├── in_memory.py │ └── pyperclip.py │ ├── completion │ ├── __init__.py │ ├── base.py │ ├── deduplicate.py │ ├── filesystem.py │ ├── fuzzy_completer.py │ ├── nested.py │ └── word_completer.py │ ├── contrib │ ├── __init__.py │ ├── completers │ │ ├── __init__.py │ │ └── system.py │ ├── regular_languages │ │ ├── __init__.py │ │ ├── compiler.py │ │ ├── completion.py │ │ ├── lexer.py │ │ ├── regex_parser.py │ │ └── validation.py │ ├── ssh │ │ ├── __init__.py │ │ └── server.py │ └── telnet │ │ ├── __init__.py │ │ ├── log.py │ │ ├── protocol.py │ │ └── server.py │ ├── cursor_shapes.py │ ├── data_structures.py │ ├── document.py │ ├── enums.py │ ├── eventloop │ ├── __init__.py │ ├── async_generator.py │ ├── inputhook.py │ ├── utils.py │ └── win32.py │ ├── filters │ ├── __init__.py │ ├── app.py │ ├── base.py │ ├── cli.py │ └── utils.py │ ├── formatted_text │ ├── __init__.py │ ├── ansi.py │ ├── base.py │ ├── html.py │ ├── pygments.py │ └── utils.py │ ├── history.py │ ├── input │ ├── __init__.py │ ├── ansi_escape_sequences.py │ ├── base.py │ ├── defaults.py │ ├── posix_pipe.py │ ├── posix_utils.py │ ├── typeahead.py │ ├── vt100.py │ ├── vt100_parser.py │ ├── win32.py │ └── win32_pipe.py │ ├── key_binding │ ├── __init__.py │ ├── bindings │ │ ├── __init__.py │ │ ├── auto_suggest.py │ │ ├── basic.py │ │ ├── completion.py │ │ ├── cpr.py │ │ ├── emacs.py │ │ ├── focus.py │ │ ├── mouse.py │ │ ├── named_commands.py │ │ ├── open_in_editor.py │ │ ├── page_navigation.py │ │ ├── scroll.py │ │ ├── search.py │ │ └── vi.py │ ├── defaults.py │ ├── digraphs.py │ ├── emacs_state.py │ ├── key_bindings.py │ ├── key_processor.py │ └── vi_state.py │ ├── keys.py │ ├── layout │ ├── __init__.py │ ├── containers.py │ ├── controls.py │ ├── dimension.py │ ├── dummy.py │ ├── layout.py │ ├── margins.py │ ├── menus.py │ ├── mouse_handlers.py │ ├── processors.py │ ├── screen.py │ ├── scrollable_pane.py │ └── utils.py │ ├── lexers │ ├── __init__.py │ ├── base.py │ └── pygments.py │ ├── log.py │ ├── mouse_events.py │ ├── output │ ├── __init__.py │ ├── base.py │ ├── color_depth.py │ ├── conemu.py │ ├── defaults.py │ ├── flush_stdout.py │ ├── plain_text.py │ ├── vt100.py │ ├── win32.py │ └── windows10.py │ ├── patch_stdout.py │ ├── py.typed │ ├── renderer.py │ ├── search.py │ ├── selection.py │ ├── shortcuts │ ├── __init__.py │ ├── dialogs.py │ ├── progress_bar │ │ ├── __init__.py │ │ ├── base.py │ │ └── formatters.py │ ├── prompt.py │ └── utils.py │ ├── styles │ ├── __init__.py │ ├── base.py │ ├── defaults.py │ ├── named_colors.py │ ├── pygments.py │ ├── style.py │ └── style_transformation.py │ ├── token.py │ ├── utils.py │ ├── validation.py │ ├── widgets │ ├── __init__.py │ ├── base.py │ ├── dialogs.py │ ├── menus.py │ └── toolbars.py │ └── win32_types.py ├── tests ├── test_async_generator.py ├── test_buffer.py ├── test_cli.py ├── test_completion.py ├── test_document.py ├── test_filter.py ├── test_formatted_text.py ├── test_history.py ├── test_inputstream.py ├── test_key_binding.py ├── test_layout.py ├── test_memory_leaks.py ├── test_print_formatted_text.py ├── test_regular_languages.py ├── test_shortcuts.py ├── test_style.py ├── test_style_transformation.py ├── test_utils.py ├── test_vt100_output.py ├── test_widgets.py └── test_yank_nth_arg.py ├── tools ├── debug_input_cross_platform.py └── debug_vt100_input.py └── tox.ini /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: off 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: # any branch 5 | pull_request: 6 | branches: [master] 7 | 8 | env: 9 | FORCE_COLOR: 1 10 | 11 | jobs: 12 | test-ubuntu: 13 | runs-on: ubuntu-latest 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: astral-sh/setup-uv@v5 22 | with: 23 | python-version: ${{ matrix.python-version }} 24 | - name: Code formatting 25 | if: ${{ matrix.python-version == '3.13' }} 26 | run: | 27 | uvx ruff check . 28 | uvx ruff format --check . 29 | - name: Typos 30 | if: ${{ matrix.python-version == '3.13' }} 31 | run: | 32 | uvx typos . 33 | - name: Unit test 34 | run: | 35 | uvx --with . --with pytest coverage run -m pytest tests/ 36 | - name: Type Checking 37 | run: | 38 | uvx --with . --with asyncssh mypy --strict src/ --platform win32 39 | uvx --with . --with asyncssh mypy --strict src/ --platform linux 40 | uvx --with . --with asyncssh mypy --strict src/ --platform darwin 41 | - name: Validate README.md 42 | if: ${{ matrix.python-version == '3.13' }} 43 | # Ensure that the README renders correctly (required for uploading to PyPI). 44 | run: | 45 | uv pip install readme_renderer 46 | python -m readme_renderer README.rst > /dev/null 47 | - name: Run codecov 48 | run: | 49 | uvx codecov 50 | - name: Upload coverage to Codecov 51 | uses: codecov/codecov-action@v4 52 | env: 53 | CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Python 3rd Party 23 | Pipfile* 24 | 25 | # Installer logs 26 | pip-log.txt 27 | 28 | # Unit test / coverage reports 29 | .coverage 30 | .tox 31 | nosetests.xml 32 | .pytest_cache 33 | 34 | # Translations 35 | *.mo 36 | 37 | # Mr Developer 38 | .mr.developer.cfg 39 | .project 40 | .pydevproject 41 | 42 | # Generated documentation 43 | docs/_build 44 | 45 | # pycharm metadata 46 | .idea 47 | 48 | # vscode metadata 49 | .vscode 50 | 51 | # virtualenvs 52 | .venv* 53 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.11" 7 | 8 | formats: 9 | - pdf 10 | - epub 11 | 12 | sphinx: 13 | configuration: docs/conf.py 14 | 15 | python: 16 | install: 17 | - requirements: docs/requirements.txt 18 | - method: pip 19 | path: . 20 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Authors 2 | ======= 3 | 4 | Creator 5 | ------- 6 | Jonathan Slenders 7 | 8 | Contributors 9 | ------------ 10 | 11 | - Amjith Ramanujam 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Jonathan Slenders 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *rst LICENSE CHANGELOG MANIFEST.in 2 | recursive-include examples *.py 3 | recursive-include tests *.py 4 | prune examples/sample?/build 5 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - PYTHON: "C:\\Python36" 4 | PYTHON_VERSION: "3.6" 5 | - PYTHON: "C:\\Python36-x64" 6 | PYTHON_VERSION: "3.6" 7 | 8 | - PYTHON: "C:\\Python35" 9 | PYTHON_VERSION: "3.5" 10 | - PYTHON: "C:\\Python35-x64" 11 | PYTHON_VERSION: "3.5" 12 | 13 | - PYTHON: "C:\\Python34" 14 | PYTHON_VERSION: "3.4" 15 | - PYTHON: "C:\\Python34-x64" 16 | PYTHON_VERSION: "3.4" 17 | 18 | - PYTHON: "C:\\Python33" 19 | PYTHON_VERSION: "3.3" 20 | - PYTHON: "C:\\Python33-x64" 21 | PYTHON_VERSION: "3.3" 22 | 23 | - PYTHON: "C:\\Python27" 24 | PYTHON_VERSION: "2.7" 25 | - PYTHON: "C:\\Python27-x64" 26 | PYTHON_VERSION: "2.7" 27 | 28 | - PYTHON: "C:\\Python26" 29 | PYTHON_VERSION: "2.6" 30 | - PYTHON: "C:\\Python27-x64" 31 | PYTHON_VERSION: "2.6" 32 | 33 | install: 34 | - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" 35 | - pip install . pytest coverage codecov flake8 36 | - pip list 37 | 38 | build: false 39 | 40 | test_script: 41 | - If not ($env:PYTHON_VERSION==2.6) flake8 prompt_toolkit 42 | - coverage run -m pytest 43 | 44 | after_test: 45 | - codecov 46 | -------------------------------------------------------------------------------- /docs/images/auto-suggestion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/auto-suggestion.png -------------------------------------------------------------------------------- /docs/images/bottom-toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/bottom-toolbar.png -------------------------------------------------------------------------------- /docs/images/colored-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/colored-prompt.png -------------------------------------------------------------------------------- /docs/images/colorful-completions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/colorful-completions.png -------------------------------------------------------------------------------- /docs/images/dialogs/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/dialogs/button.png -------------------------------------------------------------------------------- /docs/images/dialogs/confirm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/dialogs/confirm.png -------------------------------------------------------------------------------- /docs/images/dialogs/inputbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/dialogs/inputbox.png -------------------------------------------------------------------------------- /docs/images/dialogs/messagebox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/dialogs/messagebox.png -------------------------------------------------------------------------------- /docs/images/dialogs/styled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/dialogs/styled.png -------------------------------------------------------------------------------- /docs/images/hello-world-prompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/hello-world-prompt.png -------------------------------------------------------------------------------- /docs/images/html-completion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/html-completion.png -------------------------------------------------------------------------------- /docs/images/html-input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/html-input.png -------------------------------------------------------------------------------- /docs/images/logo_400px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/logo_400px.png -------------------------------------------------------------------------------- /docs/images/multiline-input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/multiline-input.png -------------------------------------------------------------------------------- /docs/images/number-validator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/number-validator.png -------------------------------------------------------------------------------- /docs/images/progress-bars/apt-get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/progress-bars/apt-get.png -------------------------------------------------------------------------------- /docs/images/progress-bars/colored-title-and-label.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/progress-bars/colored-title-and-label.png -------------------------------------------------------------------------------- /docs/images/progress-bars/custom-key-bindings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/progress-bars/custom-key-bindings.png -------------------------------------------------------------------------------- /docs/images/progress-bars/simple-progress-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/progress-bars/simple-progress-bar.png -------------------------------------------------------------------------------- /docs/images/progress-bars/two-tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/progress-bars/two-tasks.png -------------------------------------------------------------------------------- /docs/images/ptpython-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/ptpython-2.png -------------------------------------------------------------------------------- /docs/images/ptpython-history-help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/ptpython-history-help.png -------------------------------------------------------------------------------- /docs/images/ptpython-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/ptpython-menu.png -------------------------------------------------------------------------------- /docs/images/ptpython.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/ptpython.png -------------------------------------------------------------------------------- /docs/images/pymux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/pymux.png -------------------------------------------------------------------------------- /docs/images/pyvim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/pyvim.png -------------------------------------------------------------------------------- /docs/images/repl/sqlite-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/repl/sqlite-1.png -------------------------------------------------------------------------------- /docs/images/repl/sqlite-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/repl/sqlite-2.png -------------------------------------------------------------------------------- /docs/images/repl/sqlite-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/repl/sqlite-3.png -------------------------------------------------------------------------------- /docs/images/repl/sqlite-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/repl/sqlite-4.png -------------------------------------------------------------------------------- /docs/images/repl/sqlite-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/repl/sqlite-5.png -------------------------------------------------------------------------------- /docs/images/repl/sqlite-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/repl/sqlite-6.png -------------------------------------------------------------------------------- /docs/images/rprompt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/docs/images/rprompt.png -------------------------------------------------------------------------------- /docs/pages/advanced_topics/asyncio.rst: -------------------------------------------------------------------------------- 1 | .. _asyncio: 2 | 3 | Running on top of the `asyncio` event loop 4 | ========================================== 5 | 6 | .. note:: 7 | 8 | New in prompt_toolkit 3.0. (In prompt_toolkit 2.0 this was possible using a 9 | work-around). 10 | 11 | Prompt_toolkit 3.0 uses asyncio natively. Calling ``Application.run()`` will 12 | automatically run the asyncio event loop. 13 | 14 | If however you want to run a prompt_toolkit ``Application`` within an asyncio 15 | environment, you have to call the ``run_async`` method, like this: 16 | 17 | .. code:: python 18 | 19 | from prompt_toolkit.application import Application 20 | 21 | async def main(): 22 | # Define application. 23 | application = Application( 24 | ... 25 | ) 26 | 27 | result = await application.run_async() 28 | print(result) 29 | 30 | asyncio.get_event_loop().run_until_complete(main()) 31 | -------------------------------------------------------------------------------- /docs/pages/advanced_topics/index.rst: -------------------------------------------------------------------------------- 1 | .. _advanced_topics: 2 | 3 | Advanced topics 4 | =============== 5 | 6 | .. toctree:: 7 | :caption: Contents: 8 | :maxdepth: 1 9 | 10 | key_bindings 11 | styling 12 | filters 13 | rendering_flow 14 | asyncio 15 | unit_testing 16 | input_hooks 17 | architecture 18 | rendering_pipeline 19 | -------------------------------------------------------------------------------- /docs/pages/advanced_topics/input_hooks.rst: -------------------------------------------------------------------------------- 1 | .. _input_hooks: 2 | 3 | 4 | Input hooks 5 | =========== 6 | 7 | Input hooks are a tool for inserting an external event loop into the 8 | prompt_toolkit event loop, so that the other loop can run as long as 9 | prompt_toolkit (actually asyncio) is idle. This is used in applications like 10 | `IPython `_, so that GUI toolkits can display their 11 | windows while we wait at the prompt for user input. 12 | 13 | As a consequence, we will "trampoline" back and forth between two event loops. 14 | 15 | .. note:: 16 | 17 | This will use a :class:`~asyncio.SelectorEventLoop`, not the :class: 18 | :class:`~asyncio.ProactorEventLoop` (on Windows) due to the way the 19 | implementation works (contributions are welcome to make that work). 20 | 21 | 22 | .. code:: python 23 | 24 | from prompt_toolkit.eventloop.inputhook import set_eventloop_with_inputhook 25 | 26 | def inputhook(inputhook_context): 27 | # At this point, we run the other loop. This loop is supposed to run 28 | # until either `inputhook_context.fileno` becomes ready for reading or 29 | # `inputhook_context.input_is_ready()` returns True. 30 | 31 | # A good way is to register this file descriptor in this other event 32 | # loop with a callback that stops this loop when this FD becomes ready. 33 | # There is no need to actually read anything from the FD. 34 | 35 | while True: 36 | ... 37 | 38 | set_eventloop_with_inputhook(inputhook) 39 | 40 | # Any asyncio code at this point will now use this new loop, with input 41 | # hook installed. 42 | -------------------------------------------------------------------------------- /docs/pages/gallery.rst: -------------------------------------------------------------------------------- 1 | .. _gallery: 2 | 3 | Gallery 4 | ======= 5 | 6 | Showcase, demonstrating the possibilities of prompt_toolkit. 7 | 8 | Ptpython, a Python REPL 9 | ^^^^^^^^^^^^^^^^^^^^^^^ 10 | 11 | The prompt: 12 | 13 | .. image:: ../images/ptpython.png 14 | 15 | The configuration menu of ptpython. 16 | 17 | .. image:: ../images/ptpython-menu.png 18 | 19 | The history page with its help. (This is a full-screen layout.) 20 | 21 | .. image:: ../images/ptpython-history-help.png 22 | 23 | Pyvim, a Vim clone 24 | ^^^^^^^^^^^^^^^^^^ 25 | 26 | .. image:: ../images/pyvim.png 27 | 28 | 29 | Pymux, a terminal multiplexer (like tmux) in Python 30 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 31 | 32 | .. image:: ../images/pymux.png 33 | -------------------------------------------------------------------------------- /docs/pages/related_projects.rst: -------------------------------------------------------------------------------- 1 | .. _related_projects: 2 | 3 | Related projects 4 | ================ 5 | 6 | There are some other Python libraries that provide similar functionality that 7 | are also worth checking out: 8 | 9 | - `Urwid `_ 10 | - `Textual `_ 11 | - `Rich `_ 12 | -------------------------------------------------------------------------------- /docs/pages/tutorials/index.rst: -------------------------------------------------------------------------------- 1 | .. _tutorials: 2 | 3 | Tutorials 4 | ========= 5 | 6 | .. toctree:: 7 | :caption: Contents: 8 | :maxdepth: 1 9 | 10 | repl 11 | -------------------------------------------------------------------------------- /docs/pages/upgrading/index.rst: -------------------------------------------------------------------------------- 1 | .. _upgrading: 2 | 3 | Upgrading 4 | ========= 5 | 6 | .. toctree:: 7 | :caption: Contents: 8 | :maxdepth: 1 9 | 10 | 2.0 11 | 3.0 12 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx>=8,<9 2 | wcwidth<1 3 | pyperclip<2 4 | sphinx_copybutton>=0.5.2,<1.0.0 5 | sphinx-nefertiti>=0.6.0 -------------------------------------------------------------------------------- /examples/dialogs/button_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of button dialog window. 4 | """ 5 | 6 | from prompt_toolkit.shortcuts import button_dialog 7 | 8 | 9 | def main(): 10 | result = button_dialog( 11 | title="Button dialog example", 12 | text="Are you sure?", 13 | buttons=[("Yes", True), ("No", False), ("Maybe...", None)], 14 | ).run() 15 | 16 | print(f"Result = {result}") 17 | 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /examples/dialogs/checkbox_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a checkbox-list-based dialog. 4 | """ 5 | 6 | from prompt_toolkit.formatted_text import HTML 7 | from prompt_toolkit.shortcuts import checkboxlist_dialog, message_dialog 8 | from prompt_toolkit.styles import Style 9 | 10 | results = checkboxlist_dialog( 11 | title="CheckboxList dialog", 12 | text="What would you like in your breakfast ?", 13 | values=[ 14 | ("eggs", "Eggs"), 15 | ("bacon", HTML("Bacon")), 16 | ("croissants", "20 Croissants"), 17 | ("daily", "The breakfast of the day"), 18 | ], 19 | style=Style.from_dict( 20 | { 21 | "dialog": "bg:#cdbbb3", 22 | "button": "bg:#bf99a4", 23 | "checkbox": "#e8612c", 24 | "dialog.body": "bg:#a9cfd0", 25 | "dialog shadow": "bg:#c98982", 26 | "frame.label": "#fcaca3", 27 | "dialog.body label": "#fd8bb6", 28 | } 29 | ), 30 | ).run() 31 | if results: 32 | message_dialog( 33 | title="Room service", 34 | text="You selected: {}\nGreat choice sir !".format(",".join(results)), 35 | ).run() 36 | else: 37 | message_dialog("*starves*").run() 38 | -------------------------------------------------------------------------------- /examples/dialogs/input_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of an input box dialog. 4 | """ 5 | 6 | from prompt_toolkit.shortcuts import input_dialog 7 | 8 | 9 | def main(): 10 | result = input_dialog( 11 | title="Input dialog example", text="Please type your name:" 12 | ).run() 13 | 14 | print(f"Result = {result}") 15 | 16 | 17 | if __name__ == "__main__": 18 | main() 19 | -------------------------------------------------------------------------------- /examples/dialogs/messagebox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a message box window. 4 | """ 5 | 6 | from prompt_toolkit.shortcuts import message_dialog 7 | 8 | 9 | def main(): 10 | message_dialog( 11 | title="Example dialog window", 12 | text="Do you want to continue?\nPress ENTER to quit.", 13 | ).run() 14 | 15 | 16 | if __name__ == "__main__": 17 | main() 18 | -------------------------------------------------------------------------------- /examples/dialogs/password_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of an password input dialog. 4 | """ 5 | 6 | from prompt_toolkit.shortcuts import input_dialog 7 | 8 | 9 | def main(): 10 | result = input_dialog( 11 | title="Password dialog example", 12 | text="Please type your password:", 13 | password=True, 14 | ).run() 15 | 16 | print(f"Result = {result}") 17 | 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /examples/dialogs/progress_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a progress bar dialog. 4 | """ 5 | 6 | import os 7 | import time 8 | 9 | from prompt_toolkit.shortcuts import progress_dialog 10 | 11 | 12 | def worker(set_percentage, log_text): 13 | """ 14 | This worker function is called by `progress_dialog`. It will run in a 15 | background thread. 16 | 17 | The `set_percentage` function can be used to update the progress bar, while 18 | the `log_text` function can be used to log text in the logging window. 19 | """ 20 | percentage = 0 21 | for dirpath, dirnames, filenames in os.walk("../.."): 22 | for f in filenames: 23 | log_text(f"{dirpath} / {f}\n") 24 | set_percentage(percentage + 1) 25 | percentage += 2 26 | time.sleep(0.1) 27 | 28 | if percentage == 100: 29 | break 30 | if percentage == 100: 31 | break 32 | 33 | # Show 100% for a second, before quitting. 34 | set_percentage(100) 35 | time.sleep(1) 36 | 37 | 38 | def main(): 39 | progress_dialog( 40 | title="Progress dialog example", 41 | text="As an examples, we walk through the filesystem and print all directories", 42 | run_callback=worker, 43 | ).run() 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /examples/dialogs/radio_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a radio list box dialog. 4 | """ 5 | 6 | from prompt_toolkit.formatted_text import HTML 7 | from prompt_toolkit.shortcuts import radiolist_dialog 8 | 9 | 10 | def main(): 11 | result = radiolist_dialog( 12 | values=[ 13 | ("red", "Red"), 14 | ("green", "Green"), 15 | ("blue", "Blue"), 16 | ("orange", "Orange"), 17 | ], 18 | title="Radiolist dialog example", 19 | text="Please select a color:", 20 | ).run() 21 | 22 | print(f"Result = {result}") 23 | 24 | # With HTML. 25 | result = radiolist_dialog( 26 | values=[ 27 | ("red", HTML('')), 28 | ("green", HTML('')), 29 | ("blue", HTML('')), 30 | ("orange", HTML('')), 31 | ], 32 | title=HTML("Radiolist dialog example with colors"), 33 | text="Please select a color:", 34 | ).run() 35 | 36 | print(f"Result = {result}") 37 | 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /examples/dialogs/styled_messagebox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a style dialog window. 4 | All dialog shortcuts take a `style` argument in order to apply a custom 5 | styling. 6 | 7 | This also demonstrates that the `title` argument can be any kind of formatted 8 | text. 9 | """ 10 | 11 | from prompt_toolkit.formatted_text import HTML 12 | from prompt_toolkit.shortcuts import message_dialog 13 | from prompt_toolkit.styles import Style 14 | 15 | # Custom color scheme. 16 | example_style = Style.from_dict( 17 | { 18 | "dialog": "bg:#88ff88", 19 | "dialog frame-label": "bg:#ffffff #000000", 20 | "dialog.body": "bg:#000000 #00ff00", 21 | "dialog shadow": "bg:#00aa00", 22 | } 23 | ) 24 | 25 | 26 | def main(): 27 | message_dialog( 28 | title=HTML( 29 | ' ' 30 | ' window' 31 | ), 32 | text="Do you want to continue?\nPress ENTER to quit.", 33 | style=example_style, 34 | ).run() 35 | 36 | 37 | if __name__ == "__main__": 38 | main() 39 | -------------------------------------------------------------------------------- /examples/dialogs/yes_no_dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of confirmation (yes/no) dialog window. 4 | """ 5 | 6 | from prompt_toolkit.shortcuts import yes_no_dialog 7 | 8 | 9 | def main(): 10 | result = yes_no_dialog( 11 | title="Yes/No dialog example", text="Do you want to confirm?" 12 | ).run() 13 | 14 | print(f"Result = {result}") 15 | 16 | 17 | if __name__ == "__main__": 18 | main() 19 | -------------------------------------------------------------------------------- /examples/full-screen/dummy-app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This is the most simple example possible. 4 | """ 5 | 6 | from prompt_toolkit import Application 7 | 8 | app = Application(full_screen=False) 9 | app.run() 10 | -------------------------------------------------------------------------------- /examples/full-screen/hello-world.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A simple example of a a text area displaying "Hello World!". 4 | """ 5 | 6 | from prompt_toolkit.application import Application 7 | from prompt_toolkit.key_binding import KeyBindings 8 | from prompt_toolkit.layout import Layout 9 | from prompt_toolkit.widgets import Box, Frame, TextArea 10 | 11 | # Layout for displaying hello world. 12 | # (The frame creates the border, the box takes care of the margin/padding.) 13 | root_container = Box( 14 | Frame( 15 | TextArea( 16 | text="Hello world!\nPress control-c to quit.", 17 | width=40, 18 | height=10, 19 | ) 20 | ), 21 | ) 22 | layout = Layout(container=root_container) 23 | 24 | 25 | # Key bindings. 26 | kb = KeyBindings() 27 | 28 | 29 | @kb.add("c-c") 30 | def _(event): 31 | "Quit when control-c is pressed." 32 | event.app.exit() 33 | 34 | 35 | # Build a main application object. 36 | application = Application(layout=layout, key_bindings=kb, full_screen=True) 37 | 38 | 39 | def main(): 40 | application.run() 41 | 42 | 43 | if __name__ == "__main__": 44 | main() 45 | -------------------------------------------------------------------------------- /examples/full-screen/no-layout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | An empty full screen application without layout. 4 | """ 5 | 6 | from prompt_toolkit import Application 7 | 8 | Application(full_screen=True).run() 9 | -------------------------------------------------------------------------------- /examples/full-screen/scrollable-panes/simple-example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A simple example of a scrollable pane. 4 | """ 5 | 6 | from prompt_toolkit.application import Application 7 | from prompt_toolkit.application.current import get_app 8 | from prompt_toolkit.key_binding import KeyBindings 9 | from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous 10 | from prompt_toolkit.layout import Dimension, HSplit, Layout, ScrollablePane 11 | from prompt_toolkit.widgets import Frame, TextArea 12 | 13 | 14 | def main(): 15 | # Create a big layout of many text areas, then wrap them in a `ScrollablePane`. 16 | root_container = Frame( 17 | ScrollablePane( 18 | HSplit( 19 | [ 20 | Frame(TextArea(text=f"label-{i}"), width=Dimension()) 21 | for i in range(20) 22 | ] 23 | ) 24 | ) 25 | # ScrollablePane(HSplit([TextArea(text=f"label-{i}") for i in range(20)])) 26 | ) 27 | 28 | layout = Layout(container=root_container) 29 | 30 | # Key bindings. 31 | kb = KeyBindings() 32 | 33 | @kb.add("c-c") 34 | def exit(event) -> None: 35 | get_app().exit() 36 | 37 | kb.add("tab")(focus_next) 38 | kb.add("s-tab")(focus_previous) 39 | 40 | # Create and run application. 41 | application = Application(layout=layout, key_bindings=kb, full_screen=True) 42 | application.run() 43 | 44 | 45 | if __name__ == "__main__": 46 | main() 47 | -------------------------------------------------------------------------------- /examples/full-screen/simple-demos/alignment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demo of the different Window alignment options. 4 | """ 5 | 6 | from prompt_toolkit.application import Application 7 | from prompt_toolkit.key_binding import KeyBindings 8 | from prompt_toolkit.layout.containers import HSplit, Window, WindowAlign 9 | from prompt_toolkit.layout.controls import FormattedTextControl 10 | from prompt_toolkit.layout.layout import Layout 11 | 12 | LIPSUM = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas 13 | quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum 14 | mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at 15 | dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam 16 | placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut 17 | tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue 18 | risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus 19 | consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo 20 | sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed 21 | convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex 22 | quis sodales maximus.""" 23 | 24 | # 1. The layout 25 | 26 | left_text = '\nLeft aligned text. - (Press "q" to quit)\n\n' + LIPSUM 27 | center_text = "Centered text.\n\n" + LIPSUM 28 | right_text = "Right aligned text.\n\n" + LIPSUM 29 | 30 | 31 | body = HSplit( 32 | [ 33 | Window(FormattedTextControl(left_text), align=WindowAlign.LEFT), 34 | Window(height=1, char="-"), 35 | Window(FormattedTextControl(center_text), align=WindowAlign.CENTER), 36 | Window(height=1, char="-"), 37 | Window(FormattedTextControl(right_text), align=WindowAlign.RIGHT), 38 | ] 39 | ) 40 | 41 | 42 | # 2. Key bindings 43 | kb = KeyBindings() 44 | 45 | 46 | @kb.add("q") 47 | def _(event): 48 | "Quit application." 49 | event.app.exit() 50 | 51 | 52 | # 3. The `Application` 53 | application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) 54 | 55 | 56 | def run(): 57 | application.run() 58 | 59 | 60 | if __name__ == "__main__": 61 | run() 62 | -------------------------------------------------------------------------------- /examples/full-screen/simple-demos/autocompletion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | An example of a BufferControl in a full screen layout that offers auto 4 | completion. 5 | 6 | Important is to make sure that there is a `CompletionsMenu` in the layout, 7 | otherwise the completions won't be visible. 8 | """ 9 | 10 | from prompt_toolkit.application import Application 11 | from prompt_toolkit.buffer import Buffer 12 | from prompt_toolkit.completion import WordCompleter 13 | from prompt_toolkit.key_binding import KeyBindings 14 | from prompt_toolkit.layout.containers import Float, FloatContainer, HSplit, Window 15 | from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl 16 | from prompt_toolkit.layout.layout import Layout 17 | from prompt_toolkit.layout.menus import CompletionsMenu 18 | 19 | # The completer. 20 | animal_completer = WordCompleter( 21 | [ 22 | "alligator", 23 | "ant", 24 | "ape", 25 | "bat", 26 | "bear", 27 | "beaver", 28 | "bee", 29 | "bison", 30 | "butterfly", 31 | "cat", 32 | "chicken", 33 | "crocodile", 34 | "dinosaur", 35 | "dog", 36 | "dolphin", 37 | "dove", 38 | "duck", 39 | "eagle", 40 | "elephant", 41 | "fish", 42 | "goat", 43 | "gorilla", 44 | "kangaroo", 45 | "leopard", 46 | "lion", 47 | "mouse", 48 | "rabbit", 49 | "rat", 50 | "snake", 51 | "spider", 52 | "turkey", 53 | "turtle", 54 | ], 55 | ignore_case=True, 56 | ) 57 | 58 | 59 | # The layout 60 | buff = Buffer(completer=animal_completer, complete_while_typing=True) 61 | 62 | body = FloatContainer( 63 | content=HSplit( 64 | [ 65 | Window( 66 | FormattedTextControl('Press "q" to quit.'), height=1, style="reverse" 67 | ), 68 | Window(BufferControl(buffer=buff)), 69 | ] 70 | ), 71 | floats=[ 72 | Float( 73 | xcursor=True, 74 | ycursor=True, 75 | content=CompletionsMenu(max_height=16, scroll_offset=1), 76 | ) 77 | ], 78 | ) 79 | 80 | 81 | # Key bindings 82 | kb = KeyBindings() 83 | 84 | 85 | @kb.add("q") 86 | @kb.add("c-c") 87 | def _(event): 88 | "Quit application." 89 | event.app.exit() 90 | 91 | 92 | # The `Application` 93 | application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) 94 | 95 | 96 | def run(): 97 | application.run() 98 | 99 | 100 | if __name__ == "__main__": 101 | run() 102 | -------------------------------------------------------------------------------- /examples/full-screen/simple-demos/colorcolumn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Colorcolumn example. 4 | """ 5 | 6 | from prompt_toolkit.application import Application 7 | from prompt_toolkit.buffer import Buffer 8 | from prompt_toolkit.key_binding import KeyBindings 9 | from prompt_toolkit.layout.containers import ColorColumn, HSplit, Window 10 | from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl 11 | from prompt_toolkit.layout.layout import Layout 12 | 13 | LIPSUM = """ 14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas 15 | quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum 16 | mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at 17 | dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam 18 | placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut 19 | tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue 20 | risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus 21 | consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo 22 | sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed 23 | convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex 24 | quis sodales maximus.""" 25 | 26 | # Create text buffers. 27 | buff = Buffer() 28 | buff.text = LIPSUM 29 | 30 | # 1. The layout 31 | color_columns = [ 32 | ColorColumn(50), 33 | ColorColumn(80, style="bg:#ff0000"), 34 | ColorColumn(10, style="bg:#ff0000"), 35 | ] 36 | 37 | body = HSplit( 38 | [ 39 | Window(FormattedTextControl('Press "q" to quit.'), height=1, style="reverse"), 40 | Window(BufferControl(buffer=buff), colorcolumns=color_columns), 41 | ] 42 | ) 43 | 44 | 45 | # 2. Key bindings 46 | kb = KeyBindings() 47 | 48 | 49 | @kb.add("q") 50 | def _(event): 51 | "Quit application." 52 | event.app.exit() 53 | 54 | 55 | # 3. The `Application` 56 | application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) 57 | 58 | 59 | def run(): 60 | application.run() 61 | 62 | 63 | if __name__ == "__main__": 64 | run() 65 | -------------------------------------------------------------------------------- /examples/full-screen/simple-demos/cursorcolumn-cursorline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Cursorcolumn / cursorline example. 4 | """ 5 | 6 | from prompt_toolkit.application import Application 7 | from prompt_toolkit.buffer import Buffer 8 | from prompt_toolkit.key_binding import KeyBindings 9 | from prompt_toolkit.layout.containers import HSplit, Window 10 | from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl 11 | from prompt_toolkit.layout.layout import Layout 12 | 13 | LIPSUM = """ 14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas 15 | quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum 16 | mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at 17 | dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam 18 | placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut 19 | tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue 20 | risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus 21 | consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo 22 | sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed 23 | convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex 24 | quis sodales maximus.""" 25 | 26 | # Create text buffers. Cursorcolumn/cursorline are mostly combined with an 27 | # (editable) text buffers, where the user can move the cursor. 28 | 29 | buff = Buffer() 30 | buff.text = LIPSUM 31 | 32 | # 1. The layout 33 | body = HSplit( 34 | [ 35 | Window(FormattedTextControl('Press "q" to quit.'), height=1, style="reverse"), 36 | Window(BufferControl(buffer=buff), cursorcolumn=True, cursorline=True), 37 | ] 38 | ) 39 | 40 | 41 | # 2. Key bindings 42 | kb = KeyBindings() 43 | 44 | 45 | @kb.add("q") 46 | def _(event): 47 | "Quit application." 48 | event.app.exit() 49 | 50 | 51 | # 3. The `Application` 52 | application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) 53 | 54 | 55 | def run(): 56 | application.run() 57 | 58 | 59 | if __name__ == "__main__": 60 | run() 61 | -------------------------------------------------------------------------------- /examples/full-screen/simple-demos/horizontal-split.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Horizontal split example. 4 | """ 5 | 6 | from prompt_toolkit.application import Application 7 | from prompt_toolkit.key_binding import KeyBindings 8 | from prompt_toolkit.layout.containers import HSplit, Window 9 | from prompt_toolkit.layout.controls import FormattedTextControl 10 | from prompt_toolkit.layout.layout import Layout 11 | 12 | # 1. The layout 13 | left_text = "\nVertical-split example. Press 'q' to quit.\n\n(top pane.)" 14 | right_text = "\n(bottom pane.)" 15 | 16 | 17 | body = HSplit( 18 | [ 19 | Window(FormattedTextControl(left_text)), 20 | Window(height=1, char="-"), # Horizontal line in the middle. 21 | Window(FormattedTextControl(right_text)), 22 | ] 23 | ) 24 | 25 | 26 | # 2. Key bindings 27 | kb = KeyBindings() 28 | 29 | 30 | @kb.add("q") 31 | def _(event): 32 | "Quit application." 33 | event.app.exit() 34 | 35 | 36 | # 3. The `Application` 37 | application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) 38 | 39 | 40 | def run(): 41 | application.run() 42 | 43 | 44 | if __name__ == "__main__": 45 | run() 46 | -------------------------------------------------------------------------------- /examples/full-screen/simple-demos/margins.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of Window margins. 4 | 5 | This is mainly used for displaying line numbers and scroll bars, but it could 6 | be used to display any other kind of information as well. 7 | """ 8 | 9 | from prompt_toolkit.application import Application 10 | from prompt_toolkit.buffer import Buffer 11 | from prompt_toolkit.key_binding import KeyBindings 12 | from prompt_toolkit.layout.containers import HSplit, Window 13 | from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl 14 | from prompt_toolkit.layout.layout import Layout 15 | from prompt_toolkit.layout.margins import NumberedMargin, ScrollbarMargin 16 | 17 | LIPSUM = ( 18 | """ 19 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas 20 | quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum 21 | mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at 22 | dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam 23 | placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut 24 | tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue 25 | risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus 26 | consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo 27 | sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed 28 | convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex 29 | quis sodales maximus.""" 30 | * 40 31 | ) 32 | 33 | # Create text buffers. The margins will update if you scroll up or down. 34 | 35 | buff = Buffer() 36 | buff.text = LIPSUM 37 | 38 | # 1. The layout 39 | body = HSplit( 40 | [ 41 | Window(FormattedTextControl('Press "q" to quit.'), height=1, style="reverse"), 42 | Window( 43 | BufferControl(buffer=buff), 44 | # Add margins. 45 | left_margins=[NumberedMargin(), ScrollbarMargin()], 46 | right_margins=[ScrollbarMargin(), ScrollbarMargin()], 47 | ), 48 | ] 49 | ) 50 | 51 | 52 | # 2. Key bindings 53 | kb = KeyBindings() 54 | 55 | 56 | @kb.add("q") 57 | @kb.add("c-c") 58 | def _(event): 59 | "Quit application." 60 | event.app.exit() 61 | 62 | 63 | # 3. The `Application` 64 | application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) 65 | 66 | 67 | def run(): 68 | application.run() 69 | 70 | 71 | if __name__ == "__main__": 72 | run() 73 | -------------------------------------------------------------------------------- /examples/full-screen/simple-demos/vertical-split.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Vertical split example. 4 | """ 5 | 6 | from prompt_toolkit.application import Application 7 | from prompt_toolkit.key_binding import KeyBindings 8 | from prompt_toolkit.layout.containers import VSplit, Window 9 | from prompt_toolkit.layout.controls import FormattedTextControl 10 | from prompt_toolkit.layout.layout import Layout 11 | 12 | # 1. The layout 13 | left_text = "\nVertical-split example. Press 'q' to quit.\n\n(left pane.)" 14 | right_text = "\n(right pane.)" 15 | 16 | 17 | body = VSplit( 18 | [ 19 | Window(FormattedTextControl(left_text)), 20 | Window(width=1, char="|"), # Vertical line in the middle. 21 | Window(FormattedTextControl(right_text)), 22 | ] 23 | ) 24 | 25 | 26 | # 2. Key bindings 27 | kb = KeyBindings() 28 | 29 | 30 | @kb.add("q") 31 | def _(event): 32 | "Quit application." 33 | event.app.exit() 34 | 35 | 36 | # 3. The `Application` 37 | application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) 38 | 39 | 40 | def run(): 41 | application.run() 42 | 43 | 44 | if __name__ == "__main__": 45 | run() 46 | -------------------------------------------------------------------------------- /examples/gevent-get-input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | For testing: test to make sure that everything still works when gevent monkey 4 | patches are applied. 5 | """ 6 | 7 | from gevent.monkey import patch_all 8 | 9 | from prompt_toolkit.eventloop.defaults import create_event_loop 10 | from prompt_toolkit.shortcuts import PromptSession 11 | 12 | if __name__ == "__main__": 13 | # Apply patches. 14 | patch_all() 15 | 16 | # There were some issues in the past when the event loop had an input hook. 17 | def dummy_inputhook(*a): 18 | pass 19 | 20 | eventloop = create_event_loop(inputhook=dummy_inputhook) 21 | 22 | # Ask for input. 23 | session = PromptSession("Give me some input: ", loop=eventloop) 24 | answer = session.prompt() 25 | print(f"You said: {answer}") 26 | -------------------------------------------------------------------------------- /examples/print-text/ansi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of how to print using ANSI escape sequences. 4 | 5 | The advantage here is that this is cross platform. The escape sequences will be 6 | parsed and turned into appropriate Win32 API calls on Windows. 7 | """ 8 | 9 | from prompt_toolkit import print_formatted_text 10 | from prompt_toolkit.formatted_text import ANSI, HTML 11 | 12 | print = print_formatted_text 13 | 14 | 15 | def title(text): 16 | print(HTML("\n{}").format(text)) 17 | 18 | 19 | def main(): 20 | title("Special formatting") 21 | print(ANSI(" \x1b[1mBold")) 22 | print(ANSI(" \x1b[6mBlink")) 23 | print(ANSI(" \x1b[3mItalic")) 24 | print(ANSI(" \x1b[7mReverse")) 25 | print(ANSI(" \x1b[4mUnderline")) 26 | print(ANSI(" \x1b[9mStrike")) 27 | print(ANSI(" \x1b[8mHidden\x1b[0m (Hidden)")) 28 | 29 | # Ansi colors. 30 | title("ANSI colors") 31 | 32 | print(ANSI(" \x1b[91mANSI Red")) 33 | print(ANSI(" \x1b[94mANSI Blue")) 34 | 35 | # Other named colors. 36 | title("Named colors") 37 | 38 | print(ANSI(" \x1b[38;5;214morange")) 39 | print(ANSI(" \x1b[38;5;90mpurple")) 40 | 41 | # Background colors. 42 | title("Background colors") 43 | 44 | print(ANSI(" \x1b[97;101mANSI Red")) 45 | print(ANSI(" \x1b[97;104mANSI Blue")) 46 | 47 | print() 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /examples/print-text/html.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of how to print using the HTML class. 4 | """ 5 | 6 | from prompt_toolkit import HTML, print_formatted_text 7 | 8 | print = print_formatted_text 9 | 10 | 11 | def title(text): 12 | print(HTML("\n{}").format(text)) 13 | 14 | 15 | def main(): 16 | title("Special formatting") 17 | print(HTML(" Bold")) 18 | print(HTML(" Blink")) 19 | print(HTML(" Italic")) 20 | print(HTML(" Reverse")) 21 | print(HTML(" Underline")) 22 | print(HTML(" Strike")) 23 | print(HTML(" Hidden (hidden)")) 24 | 25 | # Ansi colors. 26 | title("ANSI colors") 27 | 28 | print(HTML(" ANSI Red")) 29 | print(HTML(" ANSI Blue")) 30 | 31 | # Other named colors. 32 | title("Named colors") 33 | 34 | print(HTML(" orange")) 35 | print(HTML(" purple")) 36 | 37 | # Background colors. 38 | title("Background colors") 39 | 40 | print(HTML(' ')) 41 | print(HTML(' ')) 42 | 43 | # Interpolation. 44 | title("HTML interpolation (see source)") 45 | 46 | print(HTML(" {}").format("")) 47 | print(HTML(" {text}").format(text="")) 48 | print(HTML(" %s") % ("",)) 49 | 50 | print() 51 | 52 | 53 | if __name__ == "__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /examples/print-text/named-colors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of all the ANSI colors. 4 | """ 5 | 6 | from prompt_toolkit import HTML, print_formatted_text 7 | from prompt_toolkit.formatted_text import FormattedText 8 | from prompt_toolkit.output import ColorDepth 9 | from prompt_toolkit.styles.named_colors import NAMED_COLORS 10 | 11 | print = print_formatted_text 12 | 13 | 14 | def main(): 15 | tokens = FormattedText([("fg:" + name, name + " ") for name in NAMED_COLORS]) 16 | 17 | print(HTML("\nNamed colors, using 16 color output.")) 18 | print("(Note that it doesn't really make sense to use named colors ") 19 | print("with only 16 color output.)") 20 | print(tokens, color_depth=ColorDepth.DEPTH_4_BIT) 21 | 22 | print(HTML("\nNamed colors, use 256 colors.")) 23 | print(tokens) 24 | 25 | print(HTML("\nNamed colors, using True color output.")) 26 | print(tokens, color_depth=ColorDepth.TRUE_COLOR) 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /examples/print-text/print-formatted-text.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of printing colored text to the output. 4 | """ 5 | 6 | from prompt_toolkit import print_formatted_text 7 | from prompt_toolkit.formatted_text import ANSI, HTML, FormattedText 8 | from prompt_toolkit.styles import Style 9 | 10 | print = print_formatted_text 11 | 12 | 13 | def main(): 14 | style = Style.from_dict( 15 | { 16 | "hello": "#ff0066", 17 | "world": "#44ff44 italic", 18 | } 19 | ) 20 | 21 | # Print using a a list of text fragments. 22 | text_fragments = FormattedText( 23 | [ 24 | ("class:hello", "Hello "), 25 | ("class:world", "World"), 26 | ("", "\n"), 27 | ] 28 | ) 29 | print(text_fragments, style=style) 30 | 31 | # Print using an HTML object. 32 | print(HTML("hello world\n"), style=style) 33 | 34 | # Print using an HTML object with inline styling. 35 | print( 36 | HTML( 37 | ' ' 38 | '\n' 39 | ) 40 | ) 41 | 42 | # Print using ANSI escape sequences. 43 | print(ANSI("\x1b[31mhello \x1b[32mworld\n")) 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /examples/print-text/print-frame.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example usage of 'print_container', a tool to print 4 | any layout in a non-interactive way. 5 | """ 6 | 7 | from prompt_toolkit.shortcuts import print_container 8 | from prompt_toolkit.widgets import Frame, TextArea 9 | 10 | print_container( 11 | Frame( 12 | TextArea(text="Hello world!\n"), 13 | title="Stage: parse", 14 | ) 15 | ) 16 | -------------------------------------------------------------------------------- /examples/print-text/pygments-tokens.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Printing a list of Pygments (Token, text) tuples, 4 | or an output of a Pygments lexer. 5 | """ 6 | 7 | import pygments 8 | from pygments.lexers.python import PythonLexer 9 | from pygments.token import Token 10 | 11 | from prompt_toolkit import print_formatted_text 12 | from prompt_toolkit.formatted_text import PygmentsTokens 13 | from prompt_toolkit.styles import Style 14 | 15 | 16 | def main(): 17 | # Printing a manually constructed list of (Token, text) tuples. 18 | text = [ 19 | (Token.Keyword, "print"), 20 | (Token.Punctuation, "("), 21 | (Token.Literal.String.Double, '"'), 22 | (Token.Literal.String.Double, "hello"), 23 | (Token.Literal.String.Double, '"'), 24 | (Token.Punctuation, ")"), 25 | (Token.Text, "\n"), 26 | ] 27 | 28 | print_formatted_text(PygmentsTokens(text)) 29 | 30 | # Printing the output of a pygments lexer. 31 | tokens = list(pygments.lex('print("Hello")', lexer=PythonLexer())) 32 | print_formatted_text(PygmentsTokens(tokens)) 33 | 34 | # With a custom style. 35 | style = Style.from_dict( 36 | { 37 | "pygments.keyword": "underline", 38 | "pygments.literal.string": "bg:#00ff00 #ffffff", 39 | } 40 | ) 41 | print_formatted_text(PygmentsTokens(tokens), style=style) 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /examples/print-text/true-color-demo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of all the ANSI colors. 4 | """ 5 | 6 | from prompt_toolkit import print_formatted_text 7 | from prompt_toolkit.formatted_text import HTML, FormattedText 8 | from prompt_toolkit.output import ColorDepth 9 | 10 | print = print_formatted_text 11 | 12 | 13 | def main(): 14 | print(HTML("\nTrue color test.")) 15 | 16 | for template in [ 17 | "bg:#{0:02x}0000", # Red. 18 | "bg:#00{0:02x}00", # Green. 19 | "bg:#0000{0:02x}", # Blue. 20 | "bg:#{0:02x}{0:02x}00", # Yellow. 21 | "bg:#{0:02x}00{0:02x}", # Magenta. 22 | "bg:#00{0:02x}{0:02x}", # Cyan. 23 | "bg:#{0:02x}{0:02x}{0:02x}", # Gray. 24 | ]: 25 | fragments = [] 26 | for i in range(0, 256, 4): 27 | fragments.append((template.format(i), " ")) 28 | 29 | print(FormattedText(fragments), color_depth=ColorDepth.DEPTH_4_BIT) 30 | print(FormattedText(fragments), color_depth=ColorDepth.DEPTH_8_BIT) 31 | print(FormattedText(fragments), color_depth=ColorDepth.DEPTH_24_BIT) 32 | print() 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /examples/progress-bar/a-lot-of-parallel-tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | More complex demonstration of what's possible with the progress bar. 4 | """ 5 | 6 | import random 7 | import threading 8 | import time 9 | 10 | from prompt_toolkit import HTML 11 | from prompt_toolkit.shortcuts import ProgressBar 12 | 13 | 14 | def main(): 15 | with ProgressBar( 16 | title=HTML("Example of many parallel tasks."), 17 | bottom_toolbar=HTML("[Control-L] clear [Control-C] abort"), 18 | ) as pb: 19 | 20 | def run_task(label, total, sleep_time): 21 | """Complete a normal run.""" 22 | for i in pb(range(total), label=label): 23 | time.sleep(sleep_time) 24 | 25 | def stop_task(label, total, sleep_time): 26 | """Stop at some random index. 27 | 28 | Breaking out of iteration at some stop index mimics how progress 29 | bars behave in cases where errors are raised. 30 | """ 31 | stop_i = random.randrange(total) 32 | bar = pb(range(total), label=label) 33 | for i in bar: 34 | if stop_i == i: 35 | bar.label = f"{label} BREAK" 36 | break 37 | time.sleep(sleep_time) 38 | 39 | threads = [] 40 | 41 | for i in range(160): 42 | label = f"Task {i}" 43 | total = random.randrange(50, 200) 44 | sleep_time = random.randrange(5, 20) / 100.0 45 | 46 | threads.append( 47 | threading.Thread( 48 | target=random.choice((run_task, stop_task)), 49 | args=(label, total, sleep_time), 50 | ) 51 | ) 52 | 53 | for t in threads: 54 | t.daemon = True 55 | t.start() 56 | 57 | # Wait for the threads to finish. We use a timeout for the join() call, 58 | # because on Windows, join cannot be interrupted by Control-C or any other 59 | # signal. 60 | for t in threads: 61 | while t.is_alive(): 62 | t.join(timeout=0.5) 63 | 64 | 65 | if __name__ == "__main__": 66 | main() 67 | -------------------------------------------------------------------------------- /examples/progress-bar/colored-title-and-label.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A progress bar that displays a formatted title above the progress bar and has a 4 | colored label. 5 | """ 6 | 7 | import time 8 | 9 | from prompt_toolkit.formatted_text import HTML 10 | from prompt_toolkit.shortcuts import ProgressBar 11 | 12 | 13 | def main(): 14 | title = HTML('Downloading ') 15 | label = HTML("some file: ") 16 | 17 | with ProgressBar(title=title) as pb: 18 | for i in pb(range(800), label=label): 19 | time.sleep(0.01) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /examples/progress-bar/custom-key-bindings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A very simple progress bar which keep track of the progress as we consume an 4 | iterator. 5 | """ 6 | 7 | import os 8 | import signal 9 | import time 10 | 11 | from prompt_toolkit import HTML 12 | from prompt_toolkit.key_binding import KeyBindings 13 | from prompt_toolkit.patch_stdout import patch_stdout 14 | from prompt_toolkit.shortcuts import ProgressBar 15 | 16 | 17 | def main(): 18 | bottom_toolbar = HTML( 19 | ' [f] Print "f" [q] Abort [x] Send Control-C.' 20 | ) 21 | 22 | # Create custom key bindings first. 23 | kb = KeyBindings() 24 | cancel = [False] 25 | 26 | @kb.add("f") 27 | def _(event): 28 | print("You pressed `f`.") 29 | 30 | @kb.add("q") 31 | def _(event): 32 | "Quit by setting cancel flag." 33 | cancel[0] = True 34 | 35 | @kb.add("x") 36 | def _(event): 37 | "Quit by sending SIGINT to the main thread." 38 | os.kill(os.getpid(), signal.SIGINT) 39 | 40 | # Use `patch_stdout`, to make sure that prints go above the 41 | # application. 42 | with patch_stdout(): 43 | with ProgressBar(key_bindings=kb, bottom_toolbar=bottom_toolbar) as pb: 44 | for i in pb(range(800)): 45 | time.sleep(0.01) 46 | 47 | if cancel[0]: 48 | break 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /examples/progress-bar/many-parallel-tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | More complex demonstration of what's possible with the progress bar. 4 | """ 5 | 6 | import threading 7 | import time 8 | 9 | from prompt_toolkit import HTML 10 | from prompt_toolkit.shortcuts import ProgressBar 11 | 12 | 13 | def main(): 14 | with ProgressBar( 15 | title=HTML("Example of many parallel tasks."), 16 | bottom_toolbar=HTML("[Control-L] clear [Control-C] abort"), 17 | ) as pb: 18 | 19 | def run_task(label, total, sleep_time): 20 | for i in pb(range(total), label=label): 21 | time.sleep(sleep_time) 22 | 23 | threads = [ 24 | threading.Thread(target=run_task, args=("First task", 50, 0.1)), 25 | threading.Thread(target=run_task, args=("Second task", 100, 0.1)), 26 | threading.Thread(target=run_task, args=("Third task", 8, 3)), 27 | threading.Thread(target=run_task, args=("Fourth task", 200, 0.1)), 28 | threading.Thread(target=run_task, args=("Fifth task", 40, 0.2)), 29 | threading.Thread(target=run_task, args=("Sixth task", 220, 0.1)), 30 | threading.Thread(target=run_task, args=("Seventh task", 85, 0.05)), 31 | threading.Thread(target=run_task, args=("Eight task", 200, 0.05)), 32 | ] 33 | 34 | for t in threads: 35 | t.daemon = True 36 | t.start() 37 | 38 | # Wait for the threads to finish. We use a timeout for the join() call, 39 | # because on Windows, join cannot be interrupted by Control-C or any other 40 | # signal. 41 | for t in threads: 42 | while t.is_alive(): 43 | t.join(timeout=0.5) 44 | 45 | 46 | if __name__ == "__main__": 47 | main() 48 | -------------------------------------------------------------------------------- /examples/progress-bar/nested-progress-bars.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of nested progress bars. 4 | """ 5 | 6 | import time 7 | 8 | from prompt_toolkit import HTML 9 | from prompt_toolkit.shortcuts import ProgressBar 10 | 11 | 12 | def main(): 13 | with ProgressBar( 14 | title=HTML('Nested progress bars'), 15 | bottom_toolbar=HTML(" [Control-L] clear [Control-C] abort"), 16 | ) as pb: 17 | for i in pb(range(6), label="Main task"): 18 | for j in pb(range(200), label=f"Subtask <{i + 1}>", remove_when_done=True): 19 | time.sleep(0.01) 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /examples/progress-bar/scrolling-task-name.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A very simple progress bar where the name of the task scrolls, because it's too long. 4 | iterator. 5 | """ 6 | 7 | import time 8 | 9 | from prompt_toolkit.shortcuts import ProgressBar 10 | 11 | 12 | def main(): 13 | with ProgressBar( 14 | title="Scrolling task name (make sure the window is not too big)." 15 | ) as pb: 16 | for i in pb( 17 | range(800), 18 | label="This is a very very very long task that requires horizontal scrolling ...", 19 | ): 20 | time.sleep(0.01) 21 | 22 | 23 | if __name__ == "__main__": 24 | main() 25 | -------------------------------------------------------------------------------- /examples/progress-bar/simple-progress-bar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A very simple progress bar which keep track of the progress as we consume an 4 | iterator. 5 | """ 6 | 7 | import time 8 | 9 | from prompt_toolkit.shortcuts import ProgressBar 10 | 11 | 12 | def main(): 13 | with ProgressBar() as pb: 14 | for i in pb(range(800)): 15 | time.sleep(0.01) 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /examples/progress-bar/styled-1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A very simple progress bar which keep track of the progress as we consume an 4 | iterator. 5 | """ 6 | 7 | import time 8 | 9 | from prompt_toolkit.shortcuts import ProgressBar 10 | from prompt_toolkit.styles import Style 11 | 12 | style = Style.from_dict( 13 | { 14 | "title": "#4444ff underline", 15 | "label": "#ff4400 bold", 16 | "percentage": "#00ff00", 17 | "bar-a": "bg:#00ff00 #004400", 18 | "bar-b": "bg:#00ff00 #000000", 19 | "bar-c": "#000000 underline", 20 | "current": "#448844", 21 | "total": "#448844", 22 | "time-elapsed": "#444488", 23 | "time-left": "bg:#88ff88 #000000", 24 | } 25 | ) 26 | 27 | 28 | def main(): 29 | with ProgressBar( 30 | style=style, title="Progress bar example with custom styling." 31 | ) as pb: 32 | for i in pb(range(1600), label="Downloading..."): 33 | time.sleep(0.01) 34 | 35 | 36 | if __name__ == "__main__": 37 | main() 38 | -------------------------------------------------------------------------------- /examples/progress-bar/styled-2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A very simple progress bar which keep track of the progress as we consume an 4 | iterator. 5 | """ 6 | 7 | import time 8 | 9 | from prompt_toolkit.formatted_text import HTML 10 | from prompt_toolkit.shortcuts import ProgressBar 11 | from prompt_toolkit.shortcuts.progress_bar import formatters 12 | from prompt_toolkit.styles import Style 13 | 14 | style = Style.from_dict( 15 | { 16 | "progressbar title": "#0000ff", 17 | "item-title": "#ff4400 underline", 18 | "percentage": "#00ff00", 19 | "bar-a": "bg:#00ff00 #004400", 20 | "bar-b": "bg:#00ff00 #000000", 21 | "bar-c": "bg:#000000 #000000", 22 | "tildes": "#444488", 23 | "time-left": "bg:#88ff88 #ffffff", 24 | "spinning-wheel": "bg:#ffff00 #000000", 25 | } 26 | ) 27 | 28 | 29 | def main(): 30 | custom_formatters = [ 31 | formatters.Label(), 32 | formatters.Text(" "), 33 | formatters.SpinningWheel(), 34 | formatters.Text(" "), 35 | formatters.Text(HTML("~~~")), 36 | formatters.Bar(sym_a="#", sym_b="#", sym_c="."), 37 | formatters.Text(" left: "), 38 | formatters.TimeLeft(), 39 | ] 40 | with ProgressBar( 41 | title="Progress bar example with custom formatter.", 42 | formatters=custom_formatters, 43 | style=style, 44 | ) as pb: 45 | for i in pb(range(20), label="Downloading..."): 46 | time.sleep(1) 47 | 48 | 49 | if __name__ == "__main__": 50 | main() 51 | -------------------------------------------------------------------------------- /examples/progress-bar/styled-apt-get-install.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Styled just like an apt-get installation. 4 | """ 5 | 6 | import time 7 | 8 | from prompt_toolkit.shortcuts import ProgressBar 9 | from prompt_toolkit.shortcuts.progress_bar import formatters 10 | from prompt_toolkit.styles import Style 11 | 12 | style = Style.from_dict( 13 | { 14 | "label": "bg:#ffff00 #000000", 15 | "percentage": "bg:#ffff00 #000000", 16 | "current": "#448844", 17 | "bar": "", 18 | } 19 | ) 20 | 21 | 22 | def main(): 23 | custom_formatters = [ 24 | formatters.Label(), 25 | formatters.Text(": [", style="class:percentage"), 26 | formatters.Percentage(), 27 | formatters.Text("]", style="class:percentage"), 28 | formatters.Text(" "), 29 | formatters.Bar(sym_a="#", sym_b="#", sym_c="."), 30 | formatters.Text(" "), 31 | ] 32 | 33 | with ProgressBar(style=style, formatters=custom_formatters) as pb: 34 | for i in pb(range(1600), label="Installing"): 35 | time.sleep(0.01) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /examples/progress-bar/styled-rainbow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A simple progress bar, visualized with rainbow colors (for fun). 4 | """ 5 | 6 | import time 7 | 8 | from prompt_toolkit.output import ColorDepth 9 | from prompt_toolkit.shortcuts import ProgressBar 10 | from prompt_toolkit.shortcuts.progress_bar import formatters 11 | from prompt_toolkit.shortcuts.prompt import confirm 12 | 13 | 14 | def main(): 15 | true_color = confirm("Yes true colors? (y/n) ") 16 | 17 | custom_formatters = [ 18 | formatters.Label(), 19 | formatters.Text(" "), 20 | formatters.Rainbow(formatters.Bar()), 21 | formatters.Text(" left: "), 22 | formatters.Rainbow(formatters.TimeLeft()), 23 | ] 24 | 25 | if true_color: 26 | color_depth = ColorDepth.DEPTH_24_BIT 27 | else: 28 | color_depth = ColorDepth.DEPTH_8_BIT 29 | 30 | with ProgressBar(formatters=custom_formatters, color_depth=color_depth) as pb: 31 | for i in pb(range(20), label="Downloading..."): 32 | time.sleep(1) 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /examples/progress-bar/styled-tqdm-1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Styled similar to tqdm, another progress bar implementation in Python. 4 | 5 | See: https://github.com/noamraph/tqdm 6 | """ 7 | 8 | import time 9 | 10 | from prompt_toolkit.shortcuts import ProgressBar 11 | from prompt_toolkit.shortcuts.progress_bar import formatters 12 | from prompt_toolkit.styles import Style 13 | 14 | style = Style.from_dict({"": "cyan"}) 15 | 16 | 17 | def main(): 18 | custom_formatters = [ 19 | formatters.Label(suffix=": "), 20 | formatters.Bar(start="|", end="|", sym_a="#", sym_b="#", sym_c="-"), 21 | formatters.Text(" "), 22 | formatters.Progress(), 23 | formatters.Text(" "), 24 | formatters.Percentage(), 25 | formatters.Text(" [elapsed: "), 26 | formatters.TimeElapsed(), 27 | formatters.Text(" left: "), 28 | formatters.TimeLeft(), 29 | formatters.Text(", "), 30 | formatters.IterationsPerSecond(), 31 | formatters.Text(" iters/sec]"), 32 | formatters.Text(" "), 33 | ] 34 | 35 | with ProgressBar(style=style, formatters=custom_formatters) as pb: 36 | for i in pb(range(1600), label="Installing"): 37 | time.sleep(0.01) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /examples/progress-bar/styled-tqdm-2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Styled similar to tqdm, another progress bar implementation in Python. 4 | 5 | See: https://github.com/noamraph/tqdm 6 | """ 7 | 8 | import time 9 | 10 | from prompt_toolkit.shortcuts import ProgressBar 11 | from prompt_toolkit.shortcuts.progress_bar import formatters 12 | from prompt_toolkit.styles import Style 13 | 14 | style = Style.from_dict({"bar-a": "reverse"}) 15 | 16 | 17 | def main(): 18 | custom_formatters = [ 19 | formatters.Label(suffix=": "), 20 | formatters.Percentage(), 21 | formatters.Bar(start="|", end="|", sym_a=" ", sym_b=" ", sym_c=" "), 22 | formatters.Text(" "), 23 | formatters.Progress(), 24 | formatters.Text(" ["), 25 | formatters.TimeElapsed(), 26 | formatters.Text("<"), 27 | formatters.TimeLeft(), 28 | formatters.Text(", "), 29 | formatters.IterationsPerSecond(), 30 | formatters.Text("it/s]"), 31 | ] 32 | 33 | with ProgressBar(style=style, formatters=custom_formatters) as pb: 34 | for i in pb(range(1600), label="Installing"): 35 | time.sleep(0.01) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /examples/progress-bar/two-tasks.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Two progress bars that run in parallel. 4 | """ 5 | 6 | import threading 7 | import time 8 | 9 | from prompt_toolkit.shortcuts import ProgressBar 10 | 11 | 12 | def main(): 13 | with ProgressBar() as pb: 14 | # Two parallal tasks. 15 | def task_1(): 16 | for i in pb(range(100)): 17 | time.sleep(0.05) 18 | 19 | def task_2(): 20 | for i in pb(range(150)): 21 | time.sleep(0.08) 22 | 23 | # Start threads. 24 | t1 = threading.Thread(target=task_1) 25 | t2 = threading.Thread(target=task_2) 26 | t1.daemon = True 27 | t2.daemon = True 28 | t1.start() 29 | t2.start() 30 | 31 | # Wait for the threads to finish. We use a timeout for the join() call, 32 | # because on Windows, join cannot be interrupted by Control-C or any other 33 | # signal. 34 | for t in [t1, t2]: 35 | while t.is_alive(): 36 | t.join(timeout=0.5) 37 | 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /examples/progress-bar/unknown-length.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A very simple progress bar which keep track of the progress as we consume an 4 | iterator. 5 | """ 6 | 7 | import time 8 | 9 | from prompt_toolkit.shortcuts import ProgressBar 10 | 11 | 12 | def data(): 13 | """ 14 | A generator that produces items. len() doesn't work here, so the progress 15 | bar can't estimate the time it will take. 16 | """ 17 | yield from range(1000) 18 | 19 | 20 | def main(): 21 | with ProgressBar() as pb: 22 | for i in pb(data()): 23 | time.sleep(0.1) 24 | 25 | 26 | if __name__ == "__main__": 27 | main() 28 | -------------------------------------------------------------------------------- /examples/prompts/accept-default.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of `accept_default`, a way to automatically accept the input that the 4 | user typed without allowing him/her to edit it. 5 | 6 | This should display the prompt with all the formatting like usual, but not 7 | allow any editing. 8 | """ 9 | 10 | from prompt_toolkit import HTML, prompt 11 | 12 | if __name__ == "__main__": 13 | answer = prompt( 14 | HTML("Type some input: "), accept_default=True, default="test" 15 | ) 16 | 17 | print(f"You said: {answer}") 18 | -------------------------------------------------------------------------------- /examples/prompts/asyncio-prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This is an example of how to prompt inside an application that uses the asyncio 4 | eventloop. The ``prompt_toolkit`` library will make sure that when other 5 | coroutines are writing to stdout, they write above the prompt, not destroying 6 | the input line. 7 | This example does several things: 8 | 1. It starts a simple coroutine, printing a counter to stdout every second. 9 | 2. It starts a simple input/echo app loop which reads from stdin. 10 | Very important is the following patch. If you are passing stdin by reference to 11 | other parts of the code, make sure that this patch is applied as early as 12 | possible. :: 13 | sys.stdout = app.stdout_proxy() 14 | """ 15 | 16 | import asyncio 17 | 18 | from prompt_toolkit.patch_stdout import patch_stdout 19 | from prompt_toolkit.shortcuts import PromptSession 20 | 21 | 22 | async def print_counter(): 23 | """ 24 | Coroutine that prints counters. 25 | """ 26 | try: 27 | i = 0 28 | while True: 29 | print(f"Counter: {i}") 30 | i += 1 31 | await asyncio.sleep(3) 32 | except asyncio.CancelledError: 33 | print("Background task cancelled.") 34 | 35 | 36 | async def interactive_shell(): 37 | """ 38 | Like `interactive_shell`, but doing things manual. 39 | """ 40 | # Create Prompt. 41 | session = PromptSession("Say something: ") 42 | 43 | # Run echo loop. Read text from stdin, and reply it back. 44 | while True: 45 | try: 46 | result = await session.prompt_async() 47 | print(f'You said: "{result}"') 48 | except (EOFError, KeyboardInterrupt): 49 | return 50 | 51 | 52 | async def main(): 53 | with patch_stdout(): 54 | background_task = asyncio.create_task(print_counter()) 55 | try: 56 | await interactive_shell() 57 | finally: 58 | background_task.cancel() 59 | print("Quitting event loop. Bye.") 60 | 61 | 62 | if __name__ == "__main__": 63 | asyncio.run(main()) 64 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/autocomplete-with-control-space.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of using the control-space key binding for auto completion. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | from prompt_toolkit.completion import WordCompleter 8 | from prompt_toolkit.key_binding import KeyBindings 9 | 10 | animal_completer = WordCompleter( 11 | [ 12 | "alligator", 13 | "ant", 14 | "ape", 15 | "bat", 16 | "bear", 17 | "beaver", 18 | "bee", 19 | "bison", 20 | "butterfly", 21 | "cat", 22 | "chicken", 23 | "crocodile", 24 | "dinosaur", 25 | "dog", 26 | "dolphin", 27 | "dove", 28 | "duck", 29 | "eagle", 30 | "elephant", 31 | "fish", 32 | "goat", 33 | "gorilla", 34 | "kangaroo", 35 | "leopard", 36 | "lion", 37 | "mouse", 38 | "rabbit", 39 | "rat", 40 | "snake", 41 | "spider", 42 | "turkey", 43 | "turtle", 44 | ], 45 | ignore_case=True, 46 | ) 47 | 48 | 49 | kb = KeyBindings() 50 | 51 | 52 | @kb.add("c-space") 53 | def _(event): 54 | """ 55 | Start auto completion. If the menu is showing already, select the next 56 | completion. 57 | """ 58 | b = event.app.current_buffer 59 | if b.complete_state: 60 | b.complete_next() 61 | else: 62 | b.start_completion(select_first=False) 63 | 64 | 65 | def main(): 66 | text = prompt( 67 | "Give some animals: ", 68 | completer=animal_completer, 69 | complete_while_typing=False, 70 | key_bindings=kb, 71 | ) 72 | print(f"You said: {text}") 73 | 74 | 75 | if __name__ == "__main__": 76 | main() 77 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/autocompletion-like-readline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Autocompletion example that displays the autocompletions like readline does by 4 | binding a custom handler to the Tab key. 5 | """ 6 | 7 | from prompt_toolkit.completion import WordCompleter 8 | from prompt_toolkit.shortcuts import CompleteStyle, prompt 9 | 10 | animal_completer = WordCompleter( 11 | [ 12 | "alligator", 13 | "ant", 14 | "ape", 15 | "bat", 16 | "bear", 17 | "beaver", 18 | "bee", 19 | "bison", 20 | "butterfly", 21 | "cat", 22 | "chicken", 23 | "crocodile", 24 | "dinosaur", 25 | "dog", 26 | "dolphin", 27 | "dove", 28 | "duck", 29 | "eagle", 30 | "elephant", 31 | "fish", 32 | "goat", 33 | "gorilla", 34 | "kangaroo", 35 | "leopard", 36 | "lion", 37 | "mouse", 38 | "rabbit", 39 | "rat", 40 | "snake", 41 | "spider", 42 | "turkey", 43 | "turtle", 44 | ], 45 | ignore_case=True, 46 | ) 47 | 48 | 49 | def main(): 50 | text = prompt( 51 | "Give some animals: ", 52 | completer=animal_completer, 53 | complete_style=CompleteStyle.READLINE_LIKE, 54 | ) 55 | print(f"You said: {text}") 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/autocompletion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Autocompletion example. 4 | 5 | Press [Tab] to complete the current word. 6 | - The first Tab press fills in the common part of all completions 7 | and shows all the completions. (In the menu) 8 | - Any following tab press cycles through all the possible completions. 9 | """ 10 | 11 | from prompt_toolkit import prompt 12 | from prompt_toolkit.completion import WordCompleter 13 | 14 | animal_completer = WordCompleter( 15 | [ 16 | "alligator", 17 | "ant", 18 | "ape", 19 | "bat", 20 | "bear", 21 | "beaver", 22 | "bee", 23 | "bison", 24 | "butterfly", 25 | "cat", 26 | "chicken", 27 | "crocodile", 28 | "dinosaur", 29 | "dog", 30 | "dolphin", 31 | "dove", 32 | "duck", 33 | "eagle", 34 | "elephant", 35 | "fish", 36 | "goat", 37 | "gorilla", 38 | "kangaroo", 39 | "leopard", 40 | "lion", 41 | "mouse", 42 | "rabbit", 43 | "rat", 44 | "snake", 45 | "spider", 46 | "turkey", 47 | "turtle", 48 | ], 49 | ignore_case=True, 50 | ) 51 | 52 | 53 | def main(): 54 | text = prompt( 55 | "Give some animals: ", completer=animal_completer, complete_while_typing=False 56 | ) 57 | print(f"You said: {text}") 58 | 59 | 60 | if __name__ == "__main__": 61 | main() 62 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/colored-completions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of a custom completer class and the possibility of styling 4 | completions independently. 5 | """ 6 | 7 | from prompt_toolkit.completion import Completer, Completion 8 | from prompt_toolkit.output.color_depth import ColorDepth 9 | from prompt_toolkit.shortcuts import CompleteStyle, prompt 10 | 11 | colors = [ 12 | "red", 13 | "blue", 14 | "green", 15 | "orange", 16 | "purple", 17 | "yellow", 18 | "cyan", 19 | "magenta", 20 | "pink", 21 | ] 22 | 23 | 24 | class ColorCompleter(Completer): 25 | def get_completions(self, document, complete_event): 26 | word = document.get_word_before_cursor() 27 | for color in colors: 28 | if color.startswith(word): 29 | yield Completion( 30 | color, 31 | start_position=-len(word), 32 | style="fg:" + color, 33 | selected_style="fg:white bg:" + color, 34 | ) 35 | 36 | 37 | def main(): 38 | # Simple completion menu. 39 | print("(The completion menu displays colors.)") 40 | prompt("Type a color: ", completer=ColorCompleter()) 41 | 42 | # Multi-column menu. 43 | prompt( 44 | "Type a color: ", 45 | completer=ColorCompleter(), 46 | complete_style=CompleteStyle.MULTI_COLUMN, 47 | ) 48 | 49 | # Readline-like 50 | prompt( 51 | "Type a color: ", 52 | completer=ColorCompleter(), 53 | complete_style=CompleteStyle.READLINE_LIKE, 54 | ) 55 | 56 | # Prompt with true color output. 57 | message = [ 58 | ("#cc2244", "T"), 59 | ("#bb4444", "r"), 60 | ("#996644", "u"), 61 | ("#cc8844", "e "), 62 | ("#ccaa44", "C"), 63 | ("#bbaa44", "o"), 64 | ("#99aa44", "l"), 65 | ("#778844", "o"), 66 | ("#55aa44", "r "), 67 | ("#33aa44", "p"), 68 | ("#11aa44", "r"), 69 | ("#11aa66", "o"), 70 | ("#11aa88", "m"), 71 | ("#11aaaa", "p"), 72 | ("#11aacc", "t"), 73 | ("#11aaee", ": "), 74 | ] 75 | prompt(message, completer=ColorCompleter(), color_depth=ColorDepth.TRUE_COLOR) 76 | 77 | 78 | if __name__ == "__main__": 79 | main() 80 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/combine-multiple-completers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of multiple individual completers that are combined into one. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | from prompt_toolkit.completion import WordCompleter, merge_completers 8 | 9 | animal_completer = WordCompleter( 10 | [ 11 | "alligator", 12 | "ant", 13 | "ape", 14 | "bat", 15 | "bear", 16 | "beaver", 17 | "bee", 18 | "bison", 19 | "butterfly", 20 | "cat", 21 | "chicken", 22 | "crocodile", 23 | "dinosaur", 24 | "dog", 25 | "dolphin", 26 | "dove", 27 | "duck", 28 | "eagle", 29 | "elephant", 30 | "fish", 31 | "goat", 32 | "gorilla", 33 | "kangaroo", 34 | "leopard", 35 | "lion", 36 | "mouse", 37 | "rabbit", 38 | "rat", 39 | "snake", 40 | "spider", 41 | "turkey", 42 | "turtle", 43 | ], 44 | ignore_case=True, 45 | ) 46 | 47 | color_completer = WordCompleter( 48 | [ 49 | "red", 50 | "green", 51 | "blue", 52 | "yellow", 53 | "white", 54 | "black", 55 | "orange", 56 | "gray", 57 | "pink", 58 | "purple", 59 | "cyan", 60 | "magenta", 61 | "violet", 62 | ], 63 | ignore_case=True, 64 | ) 65 | 66 | 67 | def main(): 68 | completer = merge_completers([animal_completer, color_completer]) 69 | 70 | text = prompt( 71 | "Give some animals: ", completer=completer, complete_while_typing=False 72 | ) 73 | print(f"You said: {text}") 74 | 75 | 76 | if __name__ == "__main__": 77 | main() 78 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/fuzzy-custom-completer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of a custom completer wrapped in a `FuzzyCompleter` for fuzzy 4 | matching. 5 | """ 6 | 7 | from prompt_toolkit.completion import Completer, Completion, FuzzyCompleter 8 | from prompt_toolkit.shortcuts import CompleteStyle, prompt 9 | 10 | colors = [ 11 | "red", 12 | "blue", 13 | "green", 14 | "orange", 15 | "purple", 16 | "yellow", 17 | "cyan", 18 | "magenta", 19 | "pink", 20 | ] 21 | 22 | 23 | class ColorCompleter(Completer): 24 | def get_completions(self, document, complete_event): 25 | word = document.get_word_before_cursor() 26 | for color in colors: 27 | if color.startswith(word): 28 | yield Completion( 29 | color, 30 | start_position=-len(word), 31 | style="fg:" + color, 32 | selected_style="fg:white bg:" + color, 33 | ) 34 | 35 | 36 | def main(): 37 | # Simple completion menu. 38 | print("(The completion menu displays colors.)") 39 | prompt("Type a color: ", completer=FuzzyCompleter(ColorCompleter())) 40 | 41 | # Multi-column menu. 42 | prompt( 43 | "Type a color: ", 44 | completer=FuzzyCompleter(ColorCompleter()), 45 | complete_style=CompleteStyle.MULTI_COLUMN, 46 | ) 47 | 48 | # Readline-like 49 | prompt( 50 | "Type a color: ", 51 | completer=FuzzyCompleter(ColorCompleter()), 52 | complete_style=CompleteStyle.READLINE_LIKE, 53 | ) 54 | 55 | 56 | if __name__ == "__main__": 57 | main() 58 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/fuzzy-word-completer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Autocompletion example. 4 | 5 | Press [Tab] to complete the current word. 6 | - The first Tab press fills in the common part of all completions 7 | and shows all the completions. (In the menu) 8 | - Any following tab press cycles through all the possible completions. 9 | """ 10 | 11 | from prompt_toolkit.completion import FuzzyWordCompleter 12 | from prompt_toolkit.shortcuts import prompt 13 | 14 | animal_completer = FuzzyWordCompleter( 15 | [ 16 | "alligator", 17 | "ant", 18 | "ape", 19 | "bat", 20 | "bear", 21 | "beaver", 22 | "bee", 23 | "bison", 24 | "butterfly", 25 | "cat", 26 | "chicken", 27 | "crocodile", 28 | "dinosaur", 29 | "dog", 30 | "dolphin", 31 | "dove", 32 | "duck", 33 | "eagle", 34 | "elephant", 35 | "fish", 36 | "goat", 37 | "gorilla", 38 | "kangaroo", 39 | "leopard", 40 | "lion", 41 | "mouse", 42 | "rabbit", 43 | "rat", 44 | "snake", 45 | "spider", 46 | "turkey", 47 | "turtle", 48 | ] 49 | ) 50 | 51 | 52 | def main(): 53 | text = prompt( 54 | "Give some animals: ", completer=animal_completer, complete_while_typing=True 55 | ) 56 | print(f"You said: {text}") 57 | 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/multi-column-autocompletion-with-meta.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Autocompletion example that shows meta-information alongside the completions. 4 | """ 5 | 6 | from prompt_toolkit.completion import WordCompleter 7 | from prompt_toolkit.shortcuts import CompleteStyle, prompt 8 | 9 | animal_completer = WordCompleter( 10 | [ 11 | "alligator", 12 | "ant", 13 | "ape", 14 | "bat", 15 | "bear", 16 | "beaver", 17 | "bee", 18 | "bison", 19 | "butterfly", 20 | "cat", 21 | "chicken", 22 | "crocodile", 23 | "dinosaur", 24 | "dog", 25 | "dolphin", 26 | "dove", 27 | "duck", 28 | "eagle", 29 | "elephant", 30 | ], 31 | meta_dict={ 32 | "alligator": "An alligator is a crocodilian in the genus Alligator of the family Alligatoridae.", 33 | "ant": "Ants are eusocial insects of the family Formicidae", 34 | "ape": "Apes (Hominoidea) are a branch of Old World tailless anthropoid catarrhine primates ", 35 | "bat": "Bats are mammals of the order Chiroptera", 36 | }, 37 | ignore_case=True, 38 | ) 39 | 40 | 41 | def main(): 42 | text = prompt( 43 | "Give some animals: ", 44 | completer=animal_completer, 45 | complete_style=CompleteStyle.MULTI_COLUMN, 46 | ) 47 | print(f"You said: {text}") 48 | 49 | 50 | if __name__ == "__main__": 51 | main() 52 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/multi-column-autocompletion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Similar to the autocompletion example. But display all the completions in multiple columns. 4 | """ 5 | 6 | from prompt_toolkit.completion import WordCompleter 7 | from prompt_toolkit.shortcuts import CompleteStyle, prompt 8 | 9 | animal_completer = WordCompleter( 10 | [ 11 | "alligator", 12 | "ant", 13 | "ape", 14 | "bat", 15 | "bear", 16 | "beaver", 17 | "bee", 18 | "bison", 19 | "butterfly", 20 | "cat", 21 | "chicken", 22 | "crocodile", 23 | "dinosaur", 24 | "dog", 25 | "dolphin", 26 | "dove", 27 | "duck", 28 | "eagle", 29 | "elephant", 30 | "fish", 31 | "goat", 32 | "gorilla", 33 | "kangaroo", 34 | "leopard", 35 | "lion", 36 | "mouse", 37 | "rabbit", 38 | "rat", 39 | "snake", 40 | "spider", 41 | "turkey", 42 | "turtle", 43 | ], 44 | ignore_case=True, 45 | ) 46 | 47 | 48 | def main(): 49 | text = prompt( 50 | "Give some animals: ", 51 | completer=animal_completer, 52 | complete_style=CompleteStyle.MULTI_COLUMN, 53 | ) 54 | print(f"You said: {text}") 55 | 56 | 57 | if __name__ == "__main__": 58 | main() 59 | -------------------------------------------------------------------------------- /examples/prompts/auto-completion/nested-autocompletion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of nested autocompletion. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | from prompt_toolkit.completion import NestedCompleter 8 | 9 | completer = NestedCompleter.from_nested_dict( 10 | { 11 | "show": {"version": None, "clock": None, "ip": {"interface": {"brief": None}}}, 12 | "exit": None, 13 | } 14 | ) 15 | 16 | 17 | def main(): 18 | text = prompt("Type a command: ", completer=completer) 19 | print(f"You said: {text}") 20 | 21 | 22 | if __name__ == "__main__": 23 | main() 24 | -------------------------------------------------------------------------------- /examples/prompts/auto-suggestion.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example of a CLI that demonstrates fish-style auto suggestion. 4 | 5 | When you type some input, it will match the input against the history. If One 6 | entry of the history starts with the given input, then it will show the 7 | remaining part as a suggestion. Pressing the right arrow will insert this 8 | suggestion. 9 | """ 10 | 11 | from prompt_toolkit import PromptSession 12 | from prompt_toolkit.auto_suggest import AutoSuggestFromHistory 13 | from prompt_toolkit.history import InMemoryHistory 14 | 15 | 16 | def main(): 17 | # Create some history first. (Easy for testing.) 18 | history = InMemoryHistory() 19 | history.append_string("import os") 20 | history.append_string('print("hello")') 21 | history.append_string('print("world")') 22 | history.append_string("import path") 23 | 24 | # Print help. 25 | print("This CLI has fish-style auto-suggestion enable.") 26 | print('Type for instance "pri", then you\'ll see a suggestion.') 27 | print("Press the right arrow to insert the suggestion.") 28 | print("Press Control-C to retry. Control-D to exit.") 29 | print() 30 | 31 | session = PromptSession( 32 | history=history, 33 | auto_suggest=AutoSuggestFromHistory(), 34 | enable_history_search=True, 35 | ) 36 | 37 | while True: 38 | try: 39 | text = session.prompt("Say something: ") 40 | except KeyboardInterrupt: 41 | pass # Ctrl-C pressed. Try again. 42 | else: 43 | break 44 | 45 | print(f"You said: {text}") 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | -------------------------------------------------------------------------------- /examples/prompts/autocorrection.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of implementing auto correction while typing. 4 | 5 | The word "impotr" will be corrected when the user types a space afterwards. 6 | """ 7 | 8 | from prompt_toolkit import prompt 9 | from prompt_toolkit.key_binding import KeyBindings 10 | 11 | # Database of words to be replaced by typing. 12 | corrections = { 13 | "impotr": "import", 14 | "wolrd": "world", 15 | } 16 | 17 | 18 | def main(): 19 | # We start with a `KeyBindings` for our extra key bindings. 20 | bindings = KeyBindings() 21 | 22 | # We add a custom key binding to space. 23 | @bindings.add(" ") 24 | def _(event): 25 | """ 26 | When space is pressed, we check the word before the cursor, and 27 | autocorrect that. 28 | """ 29 | b = event.app.current_buffer 30 | w = b.document.get_word_before_cursor() 31 | 32 | if w is not None: 33 | if w in corrections: 34 | b.delete_before_cursor(count=len(w)) 35 | b.insert_text(corrections[w]) 36 | 37 | b.insert_text(" ") 38 | 39 | # Read input. 40 | text = prompt("Say something: ", key_bindings=bindings) 41 | print(f"You said: {text}") 42 | 43 | 44 | if __name__ == "__main__": 45 | main() 46 | -------------------------------------------------------------------------------- /examples/prompts/bottom-toolbar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A few examples of displaying a bottom toolbar. 4 | 5 | The ``prompt`` function takes a ``bottom_toolbar`` attribute. 6 | This can be any kind of formatted text (plain text, HTML or ANSI), or 7 | it can be a callable that takes an App and returns an of these. 8 | 9 | The bottom toolbar will always receive the style 'bottom-toolbar', and the text 10 | inside will get 'bottom-toolbar.text'. These can be used to change the default 11 | style. 12 | """ 13 | 14 | import time 15 | 16 | from prompt_toolkit import prompt 17 | from prompt_toolkit.formatted_text import ANSI, HTML 18 | from prompt_toolkit.styles import Style 19 | 20 | 21 | def main(): 22 | # Example 1: fixed text. 23 | text = prompt("Say something: ", bottom_toolbar="This is a toolbar") 24 | print(f"You said: {text}") 25 | 26 | # Example 2: fixed text from a callable: 27 | def get_toolbar(): 28 | return f"Bottom toolbar: time={time.time()!r}" 29 | 30 | text = prompt("Say something: ", bottom_toolbar=get_toolbar, refresh_interval=0.5) 31 | print(f"You said: {text}") 32 | 33 | # Example 3: Using HTML: 34 | text = prompt( 35 | "Say something: ", 36 | bottom_toolbar=HTML( 37 | '(html) This is a ' 38 | ), 39 | ) 40 | print(f"You said: {text}") 41 | 42 | # Example 4: Using ANSI: 43 | text = prompt( 44 | "Say something: ", 45 | bottom_toolbar=ANSI( 46 | "(ansi): \x1b[1mThis\x1b[0m \x1b[4mis\x1b[0m a \x1b[91mtoolbar" 47 | ), 48 | ) 49 | print(f"You said: {text}") 50 | 51 | # Example 5: styling differently. 52 | style = Style.from_dict( 53 | { 54 | "bottom-toolbar": "#aaaa00 bg:#ff0000", 55 | "bottom-toolbar.text": "#aaaa44 bg:#aa4444", 56 | } 57 | ) 58 | 59 | text = prompt("Say something: ", bottom_toolbar="This is a toolbar", style=style) 60 | print(f"You said: {text}") 61 | 62 | # Example 6: Using a list of tokens. 63 | def get_bottom_toolbar(): 64 | return [ 65 | ("", " "), 66 | ("bg:#ff0000 fg:#000000", "This"), 67 | ("", " is a "), 68 | ("bg:#ff0000 fg:#000000", "toolbar"), 69 | ("", ". "), 70 | ] 71 | 72 | text = prompt("Say something: ", bottom_toolbar=get_bottom_toolbar) 73 | print(f"You said: {text}") 74 | 75 | # Example 7: multiline fixed text. 76 | text = prompt("Say something: ", bottom_toolbar="This is\na multiline toolbar") 77 | print(f"You said: {text}") 78 | 79 | 80 | if __name__ == "__main__": 81 | main() 82 | -------------------------------------------------------------------------------- /examples/prompts/clock-input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a 'dynamic' prompt. On that shows the current time in the prompt. 4 | """ 5 | 6 | import datetime 7 | 8 | from prompt_toolkit.shortcuts import prompt 9 | 10 | 11 | def get_prompt(): 12 | "Tokens to be shown before the prompt." 13 | now = datetime.datetime.now() 14 | return [ 15 | ("bg:#008800 #ffffff", f"{now.hour}:{now.minute}:{now.second}"), 16 | ("bg:cornsilk fg:maroon", " Enter something: "), 17 | ] 18 | 19 | 20 | def main(): 21 | result = prompt(get_prompt, refresh_interval=0.5) 22 | print(f"You said: {result}") 23 | 24 | 25 | if __name__ == "__main__": 26 | main() 27 | -------------------------------------------------------------------------------- /examples/prompts/colored-prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a colored prompt. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | from prompt_toolkit.formatted_text import ANSI, HTML 8 | from prompt_toolkit.styles import Style 9 | 10 | style = Style.from_dict( 11 | { 12 | # Default style. 13 | "": "#ff0066", 14 | # Prompt. 15 | "username": "#884444 italic", 16 | "at": "#00aa00", 17 | "colon": "#00aa00", 18 | "pound": "#00aa00", 19 | "host": "#000088 bg:#aaaaff", 20 | "path": "#884444 underline", 21 | # Make a selection reverse/underlined. 22 | # (Use Control-Space to select.) 23 | "selected-text": "reverse underline", 24 | } 25 | ) 26 | 27 | 28 | def example_1(): 29 | """ 30 | Style and list of (style, text) tuples. 31 | """ 32 | # Not that we can combine class names and inline styles. 33 | prompt_fragments = [ 34 | ("class:username", "john"), 35 | ("class:at", "@"), 36 | ("class:host", "localhost"), 37 | ("class:colon", ":"), 38 | ("class:path", "/user/john"), 39 | ("bg:#00aa00 #ffffff", "#"), 40 | ("", " "), 41 | ] 42 | 43 | answer = prompt(prompt_fragments, style=style) 44 | print(f"You said: {answer}") 45 | 46 | 47 | def example_2(): 48 | """ 49 | Using HTML for the formatting. 50 | """ 51 | answer = prompt( 52 | HTML( 53 | "john@" 54 | "localhost" 55 | ":" 56 | "/user/john" 57 | ' ' 58 | ), 59 | style=style, 60 | ) 61 | print(f"You said: {answer}") 62 | 63 | 64 | def example_3(): 65 | """ 66 | Using ANSI for the formatting. 67 | """ 68 | answer = prompt( 69 | ANSI("\x1b[31mjohn\x1b[0m@\x1b[44mlocalhost\x1b[0m:\x1b[4m/user/john\x1b[0m# ") 70 | ) 71 | print(f"You said: {answer}") 72 | 73 | 74 | if __name__ == "__main__": 75 | example_1() 76 | example_2() 77 | example_3() 78 | -------------------------------------------------------------------------------- /examples/prompts/confirmation-prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a confirmation prompt. 4 | """ 5 | 6 | from prompt_toolkit.shortcuts import confirm 7 | 8 | if __name__ == "__main__": 9 | answer = confirm("Should we do that?") 10 | print(f"You said: {answer}") 11 | -------------------------------------------------------------------------------- /examples/prompts/cursor-shapes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of cursor shape configurations. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | from prompt_toolkit.cursor_shapes import CursorShape, ModalCursorShapeConfig 8 | 9 | # NOTE: We pass `enable_suspend=True`, so that we can easily see what happens 10 | # to the cursor shapes when the application is suspended. 11 | 12 | prompt("(block): ", cursor=CursorShape.BLOCK, enable_suspend=True) 13 | prompt("(underline): ", cursor=CursorShape.UNDERLINE, enable_suspend=True) 14 | prompt("(beam): ", cursor=CursorShape.BEAM, enable_suspend=True) 15 | prompt( 16 | "(modal - according to vi input mode): ", 17 | cursor=ModalCursorShapeConfig(), 18 | vi_mode=True, 19 | enable_suspend=True, 20 | ) 21 | -------------------------------------------------------------------------------- /examples/prompts/custom-key-binding.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of adding a custom key binding to a prompt. 4 | """ 5 | 6 | import asyncio 7 | 8 | from prompt_toolkit import prompt 9 | from prompt_toolkit.application import in_terminal, run_in_terminal 10 | from prompt_toolkit.key_binding import KeyBindings 11 | 12 | 13 | def main(): 14 | # We start with a `KeyBindings` of default key bindings. 15 | bindings = KeyBindings() 16 | 17 | # Add our own key binding. 18 | @bindings.add("f4") 19 | def _(event): 20 | """ 21 | When F4 has been pressed. Insert "hello world" as text. 22 | """ 23 | event.app.current_buffer.insert_text("hello world") 24 | 25 | @bindings.add("x", "y") 26 | def _(event): 27 | """ 28 | (Useless, but for demoing.) 29 | Typing 'xy' will insert 'z'. 30 | 31 | Note that when you type for instance 'xa', the insertion of 'x' is 32 | postponed until the 'a' is typed. because we don't know earlier whether 33 | or not a 'y' will follow. However, prompt-toolkit should already give 34 | some visual feedback of the typed character. 35 | """ 36 | event.app.current_buffer.insert_text("z") 37 | 38 | @bindings.add("a", "b", "c") 39 | def _(event): 40 | "Typing 'abc' should insert 'd'." 41 | event.app.current_buffer.insert_text("d") 42 | 43 | @bindings.add("c-t") 44 | def _(event): 45 | """ 46 | Print 'hello world' in the terminal when ControlT is pressed. 47 | 48 | We use ``run_in_terminal``, because that ensures that the prompt is 49 | hidden right before ``print_hello`` gets executed and it's drawn again 50 | after it. (Otherwise this would destroy the output.) 51 | """ 52 | 53 | def print_hello(): 54 | print("hello world") 55 | 56 | run_in_terminal(print_hello) 57 | 58 | @bindings.add("c-k") 59 | async def _(event): 60 | """ 61 | Example of asyncio coroutine as a key binding. 62 | """ 63 | try: 64 | for i in range(5): 65 | async with in_terminal(): 66 | print("hello") 67 | await asyncio.sleep(1) 68 | except asyncio.CancelledError: 69 | print("Prompt terminated before we completed.") 70 | 71 | # Read input. 72 | print('Press F4 to insert "hello world", type "xy" to insert "z":') 73 | text = prompt("> ", key_bindings=bindings) 74 | print(f"You said: {text}") 75 | 76 | 77 | if __name__ == "__main__": 78 | main() 79 | -------------------------------------------------------------------------------- /examples/prompts/custom-lexer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | An example of a custom lexer that prints the input text in random colors. 4 | """ 5 | 6 | from prompt_toolkit.lexers import Lexer 7 | from prompt_toolkit.shortcuts import prompt 8 | from prompt_toolkit.styles.named_colors import NAMED_COLORS 9 | 10 | 11 | class RainbowLexer(Lexer): 12 | def lex_document(self, document): 13 | colors = sorted(NAMED_COLORS, key=NAMED_COLORS.get) 14 | 15 | def get_line(lineno): 16 | return [ 17 | (colors[i % len(colors)], c) 18 | for i, c in enumerate(document.lines[lineno]) 19 | ] 20 | 21 | return get_line 22 | 23 | 24 | def main(): 25 | answer = prompt("Give me some input: ", lexer=RainbowLexer()) 26 | print(f"You said: {answer}") 27 | 28 | 29 | if __name__ == "__main__": 30 | main() 31 | -------------------------------------------------------------------------------- /examples/prompts/custom-vi-operator-and-text-object.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of adding a custom Vi operator and text object. 4 | (Note that this API is not guaranteed to remain stable.) 5 | """ 6 | 7 | from prompt_toolkit import prompt 8 | from prompt_toolkit.enums import EditingMode 9 | from prompt_toolkit.key_binding import KeyBindings 10 | from prompt_toolkit.key_binding.bindings.vi import ( 11 | TextObject, 12 | create_operator_decorator, 13 | create_text_object_decorator, 14 | ) 15 | 16 | 17 | def main(): 18 | # We start with a `Registry` of default key bindings. 19 | bindings = KeyBindings() 20 | 21 | # Create the decorators to be used for registering text objects and 22 | # operators in this registry. 23 | operator = create_operator_decorator(bindings) 24 | text_object = create_text_object_decorator(bindings) 25 | 26 | # Create a custom operator. 27 | 28 | @operator("R") 29 | def _(event, text_object): 30 | "Custom operator that reverses text." 31 | buff = event.current_buffer 32 | 33 | # Get relative start/end coordinates. 34 | start, end = text_object.operator_range(buff.document) 35 | start += buff.cursor_position 36 | end += buff.cursor_position 37 | 38 | text = buff.text[start:end] 39 | text = "".join(reversed(text)) 40 | 41 | event.app.current_buffer.text = buff.text[:start] + text + buff.text[end:] 42 | 43 | # Create a text object. 44 | 45 | @text_object("A") 46 | def _(event): 47 | "A custom text object that involves everything." 48 | # Note that a `TextObject` has coordinates, relative to the cursor position. 49 | buff = event.current_buffer 50 | return TextObject( 51 | -buff.document.cursor_position, # The start. 52 | len(buff.text) - buff.document.cursor_position, 53 | ) # The end. 54 | 55 | # Read input. 56 | print('There is a custom text object "A" that applies to everything') 57 | print('and a custom operator "r" that reverses the text object.\n') 58 | 59 | print("Things that are possible:") 60 | print("- Riw - reverse inner word.") 61 | print("- yA - yank everything.") 62 | print("- RA - reverse everything.") 63 | 64 | text = prompt( 65 | "> ", default="hello world", key_bindings=bindings, editing_mode=EditingMode.VI 66 | ) 67 | print(f"You said: {text}") 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /examples/prompts/enforce-tty-input-output.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | This will display a prompt that will always use the terminal for input and 4 | output, even if sys.stdin/stdout are connected to pipes. 5 | 6 | For testing, run as: 7 | cat /dev/null | python ./enforce-tty-input-output.py > /dev/null 8 | """ 9 | 10 | from prompt_toolkit.application import create_app_session_from_tty 11 | from prompt_toolkit.shortcuts import prompt 12 | 13 | with create_app_session_from_tty(): 14 | prompt(">") 15 | -------------------------------------------------------------------------------- /examples/prompts/fancy-zsh-prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of the fancy ZSH prompt that @anki-code was using. 4 | 5 | The theme is coming from the xonsh plugin from the xxh project: 6 | https://github.com/xxh/xxh-plugin-xonsh-theme-bar 7 | 8 | See: 9 | - https://github.com/xonsh/xonsh/issues/3356 10 | - https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1111 11 | """ 12 | 13 | import datetime 14 | 15 | from prompt_toolkit import prompt 16 | from prompt_toolkit.application import get_app 17 | from prompt_toolkit.formatted_text import ( 18 | HTML, 19 | fragment_list_width, 20 | merge_formatted_text, 21 | to_formatted_text, 22 | ) 23 | from prompt_toolkit.styles import Style 24 | 25 | style = Style.from_dict( 26 | { 27 | "username": "#aaaaaa italic", 28 | "path": "#ffffff bold", 29 | "branch": "bg:#666666", 30 | "branch exclamation-mark": "#ff0000", 31 | "env": "bg:#666666", 32 | "left-part": "bg:#444444", 33 | "right-part": "bg:#444444", 34 | "padding": "bg:#444444", 35 | } 36 | ) 37 | 38 | 39 | def get_prompt() -> HTML: 40 | """ 41 | Build the prompt dynamically every time its rendered. 42 | """ 43 | left_part = HTML( 44 | "" 45 | " root " 46 | " abc " 47 | "~/.oh-my-zsh/themes" 48 | "" 49 | ) 50 | right_part = HTML( 51 | " " 52 | " master! " 53 | " py36 " 54 | " " 55 | "" 56 | ) % (datetime.datetime.now().isoformat(),) 57 | 58 | used_width = sum( 59 | [ 60 | fragment_list_width(to_formatted_text(left_part)), 61 | fragment_list_width(to_formatted_text(right_part)), 62 | ] 63 | ) 64 | 65 | total_width = get_app().output.get_size().columns 66 | padding_size = total_width - used_width 67 | 68 | padding = HTML("%s") % (" " * padding_size,) 69 | 70 | return merge_formatted_text([left_part, padding, right_part, "\n", "# "]) 71 | 72 | 73 | def main() -> None: 74 | while True: 75 | answer = prompt(get_prompt, style=style, refresh_interval=1) 76 | print(f"You said: {answer}") 77 | 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /examples/prompts/finalterm-shell-integration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Mark the start and end of the prompt with Final term (iterm2) escape sequences. 4 | See: https://iterm2.com/finalterm.html 5 | """ 6 | 7 | import sys 8 | 9 | from prompt_toolkit import prompt 10 | from prompt_toolkit.formatted_text import ANSI 11 | 12 | BEFORE_PROMPT = "\033]133;A\a" 13 | AFTER_PROMPT = "\033]133;B\a" 14 | BEFORE_OUTPUT = "\033]133;C\a" 15 | AFTER_OUTPUT = ( 16 | "\033]133;D;{command_status}\a" # command_status is the command status, 0-255 17 | ) 18 | 19 | 20 | def get_prompt_text(): 21 | # Generate the text fragments for the prompt. 22 | # Important: use the `ZeroWidthEscape` fragment only if you are sure that 23 | # writing this as raw text to the output will not introduce any 24 | # cursor movements. 25 | return [ 26 | ("[ZeroWidthEscape]", BEFORE_PROMPT), 27 | ("", "Say something: # "), 28 | ("[ZeroWidthEscape]", AFTER_PROMPT), 29 | ] 30 | 31 | 32 | if __name__ == "__main__": 33 | # Option 1: Using a `get_prompt_text` function: 34 | answer = prompt(get_prompt_text) 35 | 36 | # Option 2: Using ANSI escape sequences. 37 | before = "\001" + BEFORE_PROMPT + "\002" 38 | after = "\001" + AFTER_PROMPT + "\002" 39 | answer = prompt(ANSI(f"{before}Say something: # {after}")) 40 | 41 | # Output. 42 | sys.stdout.write(BEFORE_OUTPUT) 43 | print(f"You said: {answer}") 44 | sys.stdout.write(AFTER_OUTPUT.format(command_status=0)) 45 | -------------------------------------------------------------------------------- /examples/prompts/get-input-vi-mode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from prompt_toolkit import prompt 3 | 4 | if __name__ == "__main__": 5 | print("You have Vi keybindings here. Press [Esc] to go to navigation mode.") 6 | answer = prompt("Give me some input: ", multiline=False, vi_mode=True) 7 | print(f"You said: {answer}") 8 | -------------------------------------------------------------------------------- /examples/prompts/get-input-with-default.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a call to `prompt` with a default value. 4 | The input is pre-filled, but the user can still edit the default. 5 | """ 6 | 7 | import getpass 8 | 9 | from prompt_toolkit import prompt 10 | 11 | if __name__ == "__main__": 12 | answer = prompt("What is your name: ", default=f"{getpass.getuser()}") 13 | print(f"You said: {answer}") 14 | -------------------------------------------------------------------------------- /examples/prompts/get-input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | The most simple prompt example. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | 8 | if __name__ == "__main__": 9 | answer = prompt("Give me some input: ") 10 | print(f"You said: {answer}") 11 | -------------------------------------------------------------------------------- /examples/prompts/get-multiline-input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from prompt_toolkit import prompt 3 | from prompt_toolkit.formatted_text import HTML 4 | 5 | 6 | def prompt_continuation(width, line_number, wrap_count): 7 | """ 8 | The continuation: display line numbers and '->' before soft wraps. 9 | 10 | Notice that we can return any kind of formatted text from here. 11 | 12 | The prompt continuation doesn't have to be the same width as the prompt 13 | which is displayed before the first line, but in this example we choose to 14 | align them. The `width` input that we receive here represents the width of 15 | the prompt. 16 | """ 17 | if wrap_count > 0: 18 | return " " * (width - 3) + "-> " 19 | else: 20 | text = ("- %i - " % (line_number + 1)).rjust(width) 21 | return HTML("%s") % text 22 | 23 | 24 | if __name__ == "__main__": 25 | print("Press [Meta+Enter] or [Esc] followed by [Enter] to accept input.") 26 | answer = prompt( 27 | "Multiline input: ", multiline=True, prompt_continuation=prompt_continuation 28 | ) 29 | print(f"You said: {answer}") 30 | -------------------------------------------------------------------------------- /examples/prompts/get-password-with-toggle-display-shortcut.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | get_password function that displays asterisks instead of the actual characters. 4 | With the addition of a ControlT shortcut to hide/show the input. 5 | """ 6 | 7 | from prompt_toolkit import prompt 8 | from prompt_toolkit.filters import Condition 9 | from prompt_toolkit.key_binding import KeyBindings 10 | 11 | 12 | def main(): 13 | hidden = [True] # Nonlocal 14 | bindings = KeyBindings() 15 | 16 | @bindings.add("c-t") 17 | def _(event): 18 | "When ControlT has been pressed, toggle visibility." 19 | hidden[0] = not hidden[0] 20 | 21 | print("Type Control-T to toggle password visible.") 22 | password = prompt( 23 | "Password: ", is_password=Condition(lambda: hidden[0]), key_bindings=bindings 24 | ) 25 | print(f"You said: {password}") 26 | 27 | 28 | if __name__ == "__main__": 29 | main() 30 | -------------------------------------------------------------------------------- /examples/prompts/get-password.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from prompt_toolkit import prompt 3 | 4 | if __name__ == "__main__": 5 | password = prompt("Password: ", is_password=True) 6 | print(f"You said: {password}") 7 | -------------------------------------------------------------------------------- /examples/prompts/history/persistent-history.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example of a CLI that keeps a persistent history of all the entered 4 | strings in a file. When you run this script for a second time, pressing 5 | arrow-up will go back in history. 6 | """ 7 | 8 | from prompt_toolkit import PromptSession 9 | from prompt_toolkit.history import FileHistory 10 | 11 | 12 | def main(): 13 | our_history = FileHistory(".example-history-file") 14 | 15 | # The history needs to be passed to the `PromptSession`. It can't be passed 16 | # to the `prompt` call because only one history can be used during a 17 | # session. 18 | session = PromptSession(history=our_history) 19 | 20 | while True: 21 | text = session.prompt("Say something: ") 22 | print(f"You said: {text}") 23 | 24 | 25 | if __name__ == "__main__": 26 | main() 27 | -------------------------------------------------------------------------------- /examples/prompts/history/slow-history.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example of a custom, very slow history, that is loaded asynchronously. 4 | 5 | By wrapping it in `ThreadedHistory`, the history will load in the background 6 | without blocking any user interaction. 7 | """ 8 | 9 | import time 10 | 11 | from prompt_toolkit import PromptSession 12 | from prompt_toolkit.history import History, ThreadedHistory 13 | 14 | 15 | class SlowHistory(History): 16 | """ 17 | Example class that loads the history very slowly... 18 | """ 19 | 20 | def load_history_strings(self): 21 | for i in range(1000): 22 | time.sleep(1) # Emulate slowness. 23 | yield f"item-{i}" 24 | 25 | def store_string(self, string): 26 | pass # Don't store strings. 27 | 28 | 29 | def main(): 30 | print( 31 | "Asynchronous loading of history. Notice that the up-arrow will work " 32 | "for as far as the completions are loaded.\n" 33 | "Even when the input is accepted, loading will continue in the " 34 | "background and when the next prompt is displayed.\n" 35 | ) 36 | our_history = ThreadedHistory(SlowHistory()) 37 | 38 | # The history needs to be passed to the `PromptSession`. It can't be passed 39 | # to the `prompt` call because only one history can be used during a 40 | # session. 41 | session = PromptSession(history=our_history) 42 | 43 | while True: 44 | text = session.prompt("Say something: ") 45 | print(f"You said: {text}") 46 | 47 | 48 | if __name__ == "__main__": 49 | main() 50 | -------------------------------------------------------------------------------- /examples/prompts/html-input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example of a syntax-highlighted HTML input line. 4 | (This requires Pygments to be installed.) 5 | """ 6 | 7 | from pygments.lexers.html import HtmlLexer 8 | 9 | from prompt_toolkit import prompt 10 | from prompt_toolkit.lexers import PygmentsLexer 11 | 12 | 13 | def main(): 14 | text = prompt("Enter HTML: ", lexer=PygmentsLexer(HtmlLexer)) 15 | print(f"You said: {text}") 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /examples/prompts/input-validation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example of input validation. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | from prompt_toolkit.validation import Validator 8 | 9 | 10 | def is_valid_email(text): 11 | return "@" in text 12 | 13 | 14 | validator = Validator.from_callable( 15 | is_valid_email, 16 | error_message="Not a valid e-mail address (Does not contain an @).", 17 | move_cursor_to_end=True, 18 | ) 19 | 20 | 21 | def main(): 22 | # Validate when pressing ENTER. 23 | text = prompt( 24 | "Enter e-mail address: ", validator=validator, validate_while_typing=False 25 | ) 26 | print(f"You said: {text}") 27 | 28 | # While typing 29 | text = prompt( 30 | "Enter e-mail address: ", validator=validator, validate_while_typing=True 31 | ) 32 | print(f"You said: {text}") 33 | 34 | 35 | if __name__ == "__main__": 36 | main() 37 | -------------------------------------------------------------------------------- /examples/prompts/mouse-support.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from prompt_toolkit import prompt 3 | 4 | if __name__ == "__main__": 5 | print( 6 | "This is multiline input. press [Meta+Enter] or [Esc] followed by [Enter] to accept input." 7 | ) 8 | print("You can click with the mouse in order to select text.") 9 | answer = prompt("Multiline input: ", multiline=True, mouse_support=True) 10 | print(f"You said: {answer}") 11 | -------------------------------------------------------------------------------- /examples/prompts/multiline-prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of how the input can be indented. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | 8 | if __name__ == "__main__": 9 | answer = prompt( 10 | "Give me some input: (ESCAPE followed by ENTER to accept)\n > ", multiline=True 11 | ) 12 | print(f"You said: {answer}") 13 | -------------------------------------------------------------------------------- /examples/prompts/no-wrapping.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from prompt_toolkit import prompt 3 | 4 | if __name__ == "__main__": 5 | answer = prompt("Give me some input: ", wrap_lines=False, multiline=True) 6 | print(f"You said: {answer}") 7 | -------------------------------------------------------------------------------- /examples/prompts/operate-and-get-next.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demo of "operate-and-get-next". 4 | 5 | (Actually, this creates one prompt application, and keeps running the same app 6 | over and over again. -- For now, this is the only way to get this working.) 7 | """ 8 | 9 | from prompt_toolkit.shortcuts import PromptSession 10 | 11 | 12 | def main(): 13 | session = PromptSession("prompt> ") 14 | while True: 15 | session.prompt() 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /examples/prompts/patch-stdout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | An example that demonstrates how `patch_stdout` works. 4 | 5 | This makes sure that output from other threads doesn't disturb the rendering of 6 | the prompt, but instead is printed nicely above the prompt. 7 | """ 8 | 9 | import threading 10 | import time 11 | 12 | from prompt_toolkit import prompt 13 | from prompt_toolkit.patch_stdout import patch_stdout 14 | 15 | 16 | def main(): 17 | # Print a counter every second in another thread. 18 | running = True 19 | 20 | def thread(): 21 | i = 0 22 | while running: 23 | i += 1 24 | print(f"i={i}") 25 | time.sleep(1) 26 | 27 | t = threading.Thread(target=thread) 28 | t.daemon = True 29 | t.start() 30 | 31 | # Now read the input. The print statements of the other thread 32 | # should not disturb anything. 33 | with patch_stdout(): 34 | result = prompt("Say something: ") 35 | print(f"You said: {result}") 36 | 37 | # Stop thread. 38 | running = False 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /examples/prompts/placeholder-text.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a placeholder that's displayed as long as no input is given. 4 | """ 5 | 6 | from prompt_toolkit import prompt 7 | from prompt_toolkit.formatted_text import HTML 8 | 9 | if __name__ == "__main__": 10 | answer = prompt( 11 | "Give me some input: ", 12 | placeholder=HTML(''), 13 | ) 14 | print(f"You said: {answer}") 15 | -------------------------------------------------------------------------------- /examples/prompts/rprompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a right prompt. This is an additional prompt that is displayed on 4 | the right side of the terminal. It will be hidden automatically when the input 5 | is long enough to cover the right side of the terminal. 6 | 7 | This is similar to RPROMPT is Zsh. 8 | """ 9 | 10 | from prompt_toolkit import prompt 11 | from prompt_toolkit.formatted_text import ANSI, HTML 12 | from prompt_toolkit.styles import Style 13 | 14 | example_style = Style.from_dict( 15 | { 16 | # The 'rprompt' gets by default the 'rprompt' class. We can use this 17 | # for the styling. 18 | "rprompt": "bg:#ff0066 #ffffff", 19 | } 20 | ) 21 | 22 | 23 | def get_rprompt_text(): 24 | return [ 25 | ("", " "), 26 | ("underline", ""), 27 | ("", " "), 28 | ] 29 | 30 | 31 | def main(): 32 | # Option 1: pass a string to 'rprompt': 33 | answer = prompt("> ", rprompt=" ", style=example_style) 34 | print(f"You said: {answer}") 35 | 36 | # Option 2: pass HTML: 37 | answer = prompt("> ", rprompt=HTML(" <rprompt> "), style=example_style) 38 | print(f"You said: {answer}") 39 | 40 | # Option 3: pass ANSI: 41 | answer = prompt( 42 | "> ", rprompt=ANSI(" \x1b[4m\x1b[0m "), style=example_style 43 | ) 44 | print(f"You said: {answer}") 45 | 46 | # Option 4: Pass a callable. (This callable can either return plain text, 47 | # an HTML object, an ANSI object or a list of (style, text) 48 | # tuples. 49 | answer = prompt("> ", rprompt=get_rprompt_text, style=example_style) 50 | print(f"You said: {answer}") 51 | 52 | 53 | if __name__ == "__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /examples/prompts/swap-light-and-dark-colors.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of swapping light/dark colors in prompt_toolkit using the 4 | `swap_light_and_dark_colors` parameter. 5 | 6 | Notice that this doesn't swap foreground and background like "reverse" does. It 7 | turns light green into dark green and the other way around. Foreground and 8 | background are independent of each other. 9 | """ 10 | 11 | from pygments.lexers.html import HtmlLexer 12 | 13 | from prompt_toolkit import prompt 14 | from prompt_toolkit.completion import WordCompleter 15 | from prompt_toolkit.filters import Condition 16 | from prompt_toolkit.formatted_text import HTML 17 | from prompt_toolkit.key_binding import KeyBindings 18 | from prompt_toolkit.lexers import PygmentsLexer 19 | 20 | html_completer = WordCompleter( 21 | [ 22 | "", 23 | "
", 24 | "", 25 | "", 26 | "", 27 | "
  • ", 28 | "", 29 | "
      ", 30 | "

      ", 31 | "", 32 | "", 33 | "", 36 | "
        ", 37 | ], 38 | ignore_case=True, 39 | ) 40 | 41 | 42 | def main(): 43 | swapped = [False] # Nonlocal 44 | bindings = KeyBindings() 45 | 46 | @bindings.add("c-t") 47 | def _(event): 48 | "When ControlT has been pressed, toggle light/dark colors." 49 | swapped[0] = not swapped[0] 50 | 51 | def bottom_toolbar(): 52 | if swapped[0]: 53 | on = "on=true" 54 | else: 55 | on = "on=false" 56 | 57 | return ( 58 | HTML( 59 | 'Press ' 60 | "to swap between dark/light colors. " 61 | '' 62 | ) 63 | % on 64 | ) 65 | 66 | text = prompt( 67 | HTML(': '), 68 | completer=html_completer, 69 | complete_while_typing=True, 70 | bottom_toolbar=bottom_toolbar, 71 | key_bindings=bindings, 72 | lexer=PygmentsLexer(HtmlLexer), 73 | swap_light_and_dark_colors=Condition(lambda: swapped[0]), 74 | ) 75 | print(f"You said: {text}") 76 | 77 | 78 | if __name__ == "__main__": 79 | main() 80 | -------------------------------------------------------------------------------- /examples/prompts/switch-between-vi-emacs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example that displays how to switch between Emacs and Vi input mode. 4 | 5 | """ 6 | 7 | from prompt_toolkit import prompt 8 | from prompt_toolkit.application.current import get_app 9 | from prompt_toolkit.enums import EditingMode 10 | from prompt_toolkit.key_binding import KeyBindings 11 | 12 | 13 | def run(): 14 | # Create a `KeyBindings` that contains the default key bindings. 15 | bindings = KeyBindings() 16 | 17 | # Add an additional key binding for toggling this flag. 18 | @bindings.add("f4") 19 | def _(event): 20 | "Toggle between Emacs and Vi mode." 21 | if event.app.editing_mode == EditingMode.VI: 22 | event.app.editing_mode = EditingMode.EMACS 23 | else: 24 | event.app.editing_mode = EditingMode.VI 25 | 26 | def bottom_toolbar(): 27 | "Display the current input mode." 28 | if get_app().editing_mode == EditingMode.VI: 29 | return " [F4] Vi " 30 | else: 31 | return " [F4] Emacs " 32 | 33 | prompt("> ", key_bindings=bindings, bottom_toolbar=bottom_toolbar) 34 | 35 | 36 | if __name__ == "__main__": 37 | run() 38 | -------------------------------------------------------------------------------- /examples/prompts/system-clipboard-integration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Demonstration of a custom clipboard class. 4 | This requires the 'pyperclip' library to be installed. 5 | """ 6 | 7 | from prompt_toolkit import prompt 8 | from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard 9 | 10 | if __name__ == "__main__": 11 | print("Emacs shortcuts:") 12 | print(" Press Control-Y to paste from the system clipboard.") 13 | print(" Press Control-Space or Control-@ to enter selection mode.") 14 | print(" Press Control-W to cut to clipboard.") 15 | print("") 16 | 17 | answer = prompt("Give me some input: ", clipboard=PyperclipClipboard()) 18 | print(f"You said: {answer}") 19 | -------------------------------------------------------------------------------- /examples/prompts/system-prompt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from prompt_toolkit import prompt 3 | 4 | if __name__ == "__main__": 5 | # System prompt. 6 | print( 7 | "(1/3) If you press meta-! or esc-! at the following prompt, you can enter system commands." 8 | ) 9 | answer = prompt("Give me some input: ", enable_system_prompt=True) 10 | print(f"You said: {answer}") 11 | 12 | # Enable suspend. 13 | print("(2/3) If you press Control-Z, the application will suspend.") 14 | answer = prompt("Give me some input: ", enable_suspend=True) 15 | print(f"You said: {answer}") 16 | 17 | # Enable open_in_editor 18 | print("(3/3) If you press Control-X Control-E, the prompt will open in $EDITOR.") 19 | answer = prompt("Give me some input: ", enable_open_in_editor=True) 20 | print(f"You said: {answer}") 21 | -------------------------------------------------------------------------------- /examples/prompts/terminal-title.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from prompt_toolkit import prompt 3 | from prompt_toolkit.shortcuts import set_title 4 | 5 | if __name__ == "__main__": 6 | set_title("This is the terminal title") 7 | answer = prompt("Give me some input: ") 8 | set_title("") 9 | 10 | print(f"You said: {answer}") 11 | -------------------------------------------------------------------------------- /examples/prompts/up-arrow-partial-string-matching.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Simple example of a CLI that demonstrates up-arrow partial string matching. 4 | 5 | When you type some input, it's possible to use the up arrow to filter the 6 | history on the items starting with the given input text. 7 | """ 8 | 9 | from prompt_toolkit import PromptSession 10 | from prompt_toolkit.history import InMemoryHistory 11 | 12 | 13 | def main(): 14 | # Create some history first. (Easy for testing.) 15 | history = InMemoryHistory() 16 | history.append_string("import os") 17 | history.append_string('print("hello")') 18 | history.append_string('print("world")') 19 | history.append_string("import path") 20 | 21 | # Print help. 22 | print("This CLI has up-arrow partial string matching enabled.") 23 | print('Type for instance "pri" followed by up-arrow and you') 24 | print('get the last items starting with "pri".') 25 | print("Press Control-C to retry. Control-D to exit.") 26 | print() 27 | 28 | session = PromptSession(history=history, enable_history_search=True) 29 | 30 | while True: 31 | try: 32 | text = session.prompt("Say something: ") 33 | except KeyboardInterrupt: 34 | pass # Ctrl-C pressed. Try again. 35 | else: 36 | break 37 | 38 | print(f"You said: {text}") 39 | 40 | 41 | if __name__ == "__main__": 42 | main() 43 | -------------------------------------------------------------------------------- /examples/telnet/dialog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a telnet application that displays a dialog window. 4 | """ 5 | 6 | import logging 7 | from asyncio import Future, run 8 | 9 | from prompt_toolkit.contrib.telnet.server import TelnetServer 10 | from prompt_toolkit.shortcuts.dialogs import yes_no_dialog 11 | 12 | # Set up logging 13 | logging.basicConfig() 14 | logging.getLogger().setLevel(logging.INFO) 15 | 16 | 17 | async def interact(connection): 18 | result = await yes_no_dialog( 19 | title="Yes/no dialog demo", text="Press yes or no" 20 | ).run_async() 21 | 22 | connection.send(f"You said: {result}\n") 23 | connection.send("Bye.\n") 24 | 25 | 26 | async def main(): 27 | server = TelnetServer(interact=interact, port=2323) 28 | server.start() 29 | 30 | # Run forever. 31 | await Future() 32 | 33 | 34 | if __name__ == "__main__": 35 | run(main()) 36 | -------------------------------------------------------------------------------- /examples/telnet/hello-world.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | A simple Telnet application that asks for input and responds. 4 | 5 | The interaction function is a prompt_toolkit coroutine. 6 | Also see the `hello-world-asyncio.py` example which uses an asyncio coroutine. 7 | That is probably the preferred way if you only need Python 3 support. 8 | """ 9 | 10 | import logging 11 | from asyncio import run 12 | 13 | from prompt_toolkit.contrib.telnet.server import TelnetServer 14 | from prompt_toolkit.shortcuts import PromptSession, clear 15 | 16 | # Set up logging 17 | logging.basicConfig() 18 | logging.getLogger().setLevel(logging.INFO) 19 | 20 | 21 | async def interact(connection): 22 | clear() 23 | connection.send("Welcome!\n") 24 | 25 | # Ask for input. 26 | session = PromptSession() 27 | result = await session.prompt_async(message="Say something: ") 28 | 29 | # Send output. 30 | connection.send(f"You said: {result}\n") 31 | connection.send("Bye.\n") 32 | 33 | 34 | async def main(): 35 | server = TelnetServer(interact=interact, port=2323) 36 | await server.run() 37 | 38 | 39 | if __name__ == "__main__": 40 | run(main()) 41 | -------------------------------------------------------------------------------- /examples/telnet/toolbar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Example of a telnet application that displays a bottom toolbar and completions 4 | in the prompt. 5 | """ 6 | 7 | import logging 8 | from asyncio import run 9 | 10 | from prompt_toolkit.completion import WordCompleter 11 | from prompt_toolkit.contrib.telnet.server import TelnetServer 12 | from prompt_toolkit.shortcuts import PromptSession 13 | 14 | # Set up logging 15 | logging.basicConfig() 16 | logging.getLogger().setLevel(logging.INFO) 17 | 18 | 19 | async def interact(connection): 20 | # When a client is connected, erase the screen from the client and say 21 | # Hello. 22 | connection.send("Welcome!\n") 23 | 24 | # Display prompt with bottom toolbar. 25 | animal_completer = WordCompleter(["alligator", "ant"]) 26 | 27 | def get_toolbar(): 28 | return "Bottom toolbar..." 29 | 30 | session = PromptSession() 31 | result = await session.prompt_async( 32 | "Say something: ", bottom_toolbar=get_toolbar, completer=animal_completer 33 | ) 34 | 35 | connection.send(f"You said: {result}\n") 36 | connection.send("Bye.\n") 37 | 38 | 39 | async def main(): 40 | server = TelnetServer(interact=interact, port=2323) 41 | await server.run() 42 | 43 | 44 | if __name__ == "__main__": 45 | run(main()) 46 | -------------------------------------------------------------------------------- /examples/tutorial/README.md: -------------------------------------------------------------------------------- 1 | See http://python-prompt-toolkit.readthedocs.io/en/stable/pages/tutorials/repl.html 2 | -------------------------------------------------------------------------------- /src/prompt_toolkit/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | prompt_toolkit 3 | ============== 4 | 5 | Author: Jonathan Slenders 6 | 7 | Description: prompt_toolkit is a Library for building powerful interactive 8 | command lines in Python. It can be a replacement for GNU 9 | Readline, but it can be much more than that. 10 | 11 | See the examples directory to learn about the usage. 12 | 13 | Probably, to get started, you might also want to have a look at 14 | `prompt_toolkit.shortcuts.prompt`. 15 | """ 16 | 17 | from __future__ import annotations 18 | 19 | import re 20 | from importlib import metadata 21 | 22 | # note: this is a bit more lax than the actual pep 440 to allow for a/b/rc/dev without a number 23 | pep440 = re.compile( 24 | r"^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*)?)?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*)?)?$", 25 | re.UNICODE, 26 | ) 27 | from .application import Application 28 | from .formatted_text import ANSI, HTML 29 | from .shortcuts import PromptSession, print_formatted_text, prompt 30 | 31 | # Don't forget to update in `docs/conf.py`! 32 | __version__ = metadata.version("prompt_toolkit") 33 | 34 | assert pep440.match(__version__) 35 | 36 | # Version tuple. 37 | VERSION = tuple(int(v.rstrip("abrc")) for v in __version__.split(".")[:3]) 38 | 39 | 40 | __all__ = [ 41 | # Application. 42 | "Application", 43 | # Shortcuts. 44 | "prompt", 45 | "PromptSession", 46 | "print_formatted_text", 47 | # Formatted text. 48 | "HTML", 49 | "ANSI", 50 | # Version info. 51 | "__version__", 52 | "VERSION", 53 | ] 54 | -------------------------------------------------------------------------------- /src/prompt_toolkit/application/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .application import Application 4 | from .current import ( 5 | AppSession, 6 | create_app_session, 7 | create_app_session_from_tty, 8 | get_app, 9 | get_app_or_none, 10 | get_app_session, 11 | set_app, 12 | ) 13 | from .dummy import DummyApplication 14 | from .run_in_terminal import in_terminal, run_in_terminal 15 | 16 | __all__ = [ 17 | # Application. 18 | "Application", 19 | # Current. 20 | "AppSession", 21 | "get_app_session", 22 | "create_app_session", 23 | "create_app_session_from_tty", 24 | "get_app", 25 | "get_app_or_none", 26 | "set_app", 27 | # Dummy. 28 | "DummyApplication", 29 | # Run_in_terminal 30 | "in_terminal", 31 | "run_in_terminal", 32 | ] 33 | -------------------------------------------------------------------------------- /src/prompt_toolkit/application/dummy.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Callable 4 | 5 | from prompt_toolkit.eventloop import InputHook 6 | from prompt_toolkit.formatted_text import AnyFormattedText 7 | from prompt_toolkit.input import DummyInput 8 | from prompt_toolkit.output import DummyOutput 9 | 10 | from .application import Application 11 | 12 | __all__ = [ 13 | "DummyApplication", 14 | ] 15 | 16 | 17 | class DummyApplication(Application[None]): 18 | """ 19 | When no :class:`.Application` is running, 20 | :func:`.get_app` will run an instance of this :class:`.DummyApplication` instead. 21 | """ 22 | 23 | def __init__(self) -> None: 24 | super().__init__(output=DummyOutput(), input=DummyInput()) 25 | 26 | def run( 27 | self, 28 | pre_run: Callable[[], None] | None = None, 29 | set_exception_handler: bool = True, 30 | handle_sigint: bool = True, 31 | in_thread: bool = False, 32 | inputhook: InputHook | None = None, 33 | ) -> None: 34 | raise NotImplementedError("A DummyApplication is not supposed to run.") 35 | 36 | async def run_async( 37 | self, 38 | pre_run: Callable[[], None] | None = None, 39 | set_exception_handler: bool = True, 40 | handle_sigint: bool = True, 41 | slow_callback_duration: float = 0.5, 42 | ) -> None: 43 | raise NotImplementedError("A DummyApplication is not supposed to run.") 44 | 45 | async def run_system_command( 46 | self, 47 | command: str, 48 | wait_for_enter: bool = True, 49 | display_before_text: AnyFormattedText = "", 50 | wait_text: str = "", 51 | ) -> None: 52 | raise NotImplementedError 53 | 54 | def suspend_to_background(self, suspend_group: bool = True) -> None: 55 | raise NotImplementedError 56 | -------------------------------------------------------------------------------- /src/prompt_toolkit/clipboard/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .base import Clipboard, ClipboardData, DummyClipboard, DynamicClipboard 4 | from .in_memory import InMemoryClipboard 5 | 6 | # We are not importing `PyperclipClipboard` here, because it would require the 7 | # `pyperclip` module to be present. 8 | 9 | # from .pyperclip import PyperclipClipboard 10 | 11 | __all__ = [ 12 | "Clipboard", 13 | "ClipboardData", 14 | "DummyClipboard", 15 | "DynamicClipboard", 16 | "InMemoryClipboard", 17 | ] 18 | -------------------------------------------------------------------------------- /src/prompt_toolkit/clipboard/in_memory.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from collections import deque 4 | 5 | from .base import Clipboard, ClipboardData 6 | 7 | __all__ = [ 8 | "InMemoryClipboard", 9 | ] 10 | 11 | 12 | class InMemoryClipboard(Clipboard): 13 | """ 14 | Default clipboard implementation. 15 | Just keep the data in memory. 16 | 17 | This implements a kill-ring, for Emacs mode. 18 | """ 19 | 20 | def __init__(self, data: ClipboardData | None = None, max_size: int = 60) -> None: 21 | assert max_size >= 1 22 | 23 | self.max_size = max_size 24 | self._ring: deque[ClipboardData] = deque() 25 | 26 | if data is not None: 27 | self.set_data(data) 28 | 29 | def set_data(self, data: ClipboardData) -> None: 30 | self._ring.appendleft(data) 31 | 32 | while len(self._ring) > self.max_size: 33 | self._ring.pop() 34 | 35 | def get_data(self) -> ClipboardData: 36 | if self._ring: 37 | return self._ring[0] 38 | else: 39 | return ClipboardData() 40 | 41 | def rotate(self) -> None: 42 | if self._ring: 43 | # Add the very first item at the end. 44 | self._ring.append(self._ring.popleft()) 45 | -------------------------------------------------------------------------------- /src/prompt_toolkit/clipboard/pyperclip.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pyperclip 4 | 5 | from prompt_toolkit.selection import SelectionType 6 | 7 | from .base import Clipboard, ClipboardData 8 | 9 | __all__ = [ 10 | "PyperclipClipboard", 11 | ] 12 | 13 | 14 | class PyperclipClipboard(Clipboard): 15 | """ 16 | Clipboard that synchronizes with the Windows/Mac/Linux system clipboard, 17 | using the pyperclip module. 18 | """ 19 | 20 | def __init__(self) -> None: 21 | self._data: ClipboardData | None = None 22 | 23 | def set_data(self, data: ClipboardData) -> None: 24 | self._data = data 25 | pyperclip.copy(data.text) 26 | 27 | def get_data(self) -> ClipboardData: 28 | text = pyperclip.paste() 29 | 30 | # When the clipboard data is equal to what we copied last time, reuse 31 | # the `ClipboardData` instance. That way we're sure to keep the same 32 | # `SelectionType`. 33 | if self._data and self._data.text == text: 34 | return self._data 35 | 36 | # Pyperclip returned something else. Create a new `ClipboardData` 37 | # instance. 38 | else: 39 | return ClipboardData( 40 | text=text, 41 | type=SelectionType.LINES if "\n" in text else SelectionType.CHARACTERS, 42 | ) 43 | -------------------------------------------------------------------------------- /src/prompt_toolkit/completion/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .base import ( 4 | CompleteEvent, 5 | Completer, 6 | Completion, 7 | ConditionalCompleter, 8 | DummyCompleter, 9 | DynamicCompleter, 10 | ThreadedCompleter, 11 | get_common_complete_suffix, 12 | merge_completers, 13 | ) 14 | from .deduplicate import DeduplicateCompleter 15 | from .filesystem import ExecutableCompleter, PathCompleter 16 | from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter 17 | from .nested import NestedCompleter 18 | from .word_completer import WordCompleter 19 | 20 | __all__ = [ 21 | # Base. 22 | "Completion", 23 | "Completer", 24 | "ThreadedCompleter", 25 | "DummyCompleter", 26 | "DynamicCompleter", 27 | "CompleteEvent", 28 | "ConditionalCompleter", 29 | "merge_completers", 30 | "get_common_complete_suffix", 31 | # Filesystem. 32 | "PathCompleter", 33 | "ExecutableCompleter", 34 | # Fuzzy 35 | "FuzzyCompleter", 36 | "FuzzyWordCompleter", 37 | # Nested. 38 | "NestedCompleter", 39 | # Word completer. 40 | "WordCompleter", 41 | # Deduplicate 42 | "DeduplicateCompleter", 43 | ] 44 | -------------------------------------------------------------------------------- /src/prompt_toolkit/completion/deduplicate.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Iterable 4 | 5 | from prompt_toolkit.document import Document 6 | 7 | from .base import CompleteEvent, Completer, Completion 8 | 9 | __all__ = ["DeduplicateCompleter"] 10 | 11 | 12 | class DeduplicateCompleter(Completer): 13 | """ 14 | Wrapper around a completer that removes duplicates. Only the first unique 15 | completions are kept. 16 | 17 | Completions are considered to be a duplicate if they result in the same 18 | document text when they would be applied. 19 | """ 20 | 21 | def __init__(self, completer: Completer) -> None: 22 | self.completer = completer 23 | 24 | def get_completions( 25 | self, document: Document, complete_event: CompleteEvent 26 | ) -> Iterable[Completion]: 27 | # Keep track of the document strings we'd get after applying any completion. 28 | found_so_far: set[str] = set() 29 | 30 | for completion in self.completer.get_completions(document, complete_event): 31 | text_if_applied = ( 32 | document.text[: document.cursor_position + completion.start_position] 33 | + completion.text 34 | + document.text[document.cursor_position :] 35 | ) 36 | 37 | if text_if_applied == document.text: 38 | # Don't include completions that don't have any effect at all. 39 | continue 40 | 41 | if text_if_applied in found_so_far: 42 | continue 43 | 44 | found_so_far.add(text_if_applied) 45 | yield completion 46 | -------------------------------------------------------------------------------- /src/prompt_toolkit/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/src/prompt_toolkit/contrib/__init__.py -------------------------------------------------------------------------------- /src/prompt_toolkit/contrib/completers/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .system import SystemCompleter 4 | 5 | __all__ = ["SystemCompleter"] 6 | -------------------------------------------------------------------------------- /src/prompt_toolkit/contrib/completers/system.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from prompt_toolkit.completion.filesystem import ExecutableCompleter, PathCompleter 4 | from prompt_toolkit.contrib.regular_languages.compiler import compile 5 | from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter 6 | 7 | __all__ = [ 8 | "SystemCompleter", 9 | ] 10 | 11 | 12 | class SystemCompleter(GrammarCompleter): 13 | """ 14 | Completer for system commands. 15 | """ 16 | 17 | def __init__(self) -> None: 18 | # Compile grammar. 19 | g = compile( 20 | r""" 21 | # First we have an executable. 22 | (?P[^\s]+) 23 | 24 | # Ignore literals in between. 25 | ( 26 | \s+ 27 | ("[^"]*" | '[^']*' | [^'"]+ ) 28 | )* 29 | 30 | \s+ 31 | 32 | # Filename as parameters. 33 | ( 34 | (?P[^\s]+) | 35 | "(?P[^\s]+)" | 36 | '(?P[^\s]+)' 37 | ) 38 | """, 39 | escape_funcs={ 40 | "double_quoted_filename": (lambda string: string.replace('"', '\\"')), 41 | "single_quoted_filename": (lambda string: string.replace("'", "\\'")), 42 | }, 43 | unescape_funcs={ 44 | "double_quoted_filename": ( 45 | lambda string: string.replace('\\"', '"') 46 | ), # XXX: not entirely correct. 47 | "single_quoted_filename": (lambda string: string.replace("\\'", "'")), 48 | }, 49 | ) 50 | 51 | # Create GrammarCompleter 52 | super().__init__( 53 | g, 54 | { 55 | "executable": ExecutableCompleter(), 56 | "filename": PathCompleter(only_directories=False, expanduser=True), 57 | "double_quoted_filename": PathCompleter( 58 | only_directories=False, expanduser=True 59 | ), 60 | "single_quoted_filename": PathCompleter( 61 | only_directories=False, expanduser=True 62 | ), 63 | }, 64 | ) 65 | -------------------------------------------------------------------------------- /src/prompt_toolkit/contrib/regular_languages/validation.py: -------------------------------------------------------------------------------- 1 | """ 2 | Validator for a regular language. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | from prompt_toolkit.document import Document 8 | from prompt_toolkit.validation import ValidationError, Validator 9 | 10 | from .compiler import _CompiledGrammar 11 | 12 | __all__ = [ 13 | "GrammarValidator", 14 | ] 15 | 16 | 17 | class GrammarValidator(Validator): 18 | """ 19 | Validator which can be used for validation according to variables in 20 | the grammar. Each variable can have its own validator. 21 | 22 | :param compiled_grammar: `GrammarCompleter` instance. 23 | :param validators: `dict` mapping variable names of the grammar to the 24 | `Validator` instances to be used for each variable. 25 | """ 26 | 27 | def __init__( 28 | self, compiled_grammar: _CompiledGrammar, validators: dict[str, Validator] 29 | ) -> None: 30 | self.compiled_grammar = compiled_grammar 31 | self.validators = validators 32 | 33 | def validate(self, document: Document) -> None: 34 | # Parse input document. 35 | # We use `match`, not `match_prefix`, because for validation, we want 36 | # the actual, unambiguous interpretation of the input. 37 | m = self.compiled_grammar.match(document.text) 38 | 39 | if m: 40 | for v in m.variables(): 41 | validator = self.validators.get(v.varname) 42 | 43 | if validator: 44 | # Unescape text. 45 | unwrapped_text = self.compiled_grammar.unescape(v.varname, v.value) 46 | 47 | # Create a document, for the completions API (text/cursor_position) 48 | inner_document = Document(unwrapped_text, len(unwrapped_text)) 49 | 50 | try: 51 | validator.validate(inner_document) 52 | except ValidationError as e: 53 | raise ValidationError( 54 | cursor_position=v.start + e.cursor_position, 55 | message=e.message, 56 | ) from e 57 | else: 58 | raise ValidationError( 59 | cursor_position=len(document.text), message="Invalid command" 60 | ) 61 | -------------------------------------------------------------------------------- /src/prompt_toolkit/contrib/ssh/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .server import PromptToolkitSSHServer, PromptToolkitSSHSession 4 | 5 | __all__ = [ 6 | "PromptToolkitSSHSession", 7 | "PromptToolkitSSHServer", 8 | ] 9 | -------------------------------------------------------------------------------- /src/prompt_toolkit/contrib/telnet/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .server import TelnetServer 4 | 5 | __all__ = [ 6 | "TelnetServer", 7 | ] 8 | -------------------------------------------------------------------------------- /src/prompt_toolkit/contrib/telnet/log.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python logger for the telnet server. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import logging 8 | 9 | logger = logging.getLogger(__package__) 10 | 11 | __all__ = [ 12 | "logger", 13 | ] 14 | -------------------------------------------------------------------------------- /src/prompt_toolkit/data_structures.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import NamedTuple 4 | 5 | __all__ = [ 6 | "Point", 7 | "Size", 8 | ] 9 | 10 | 11 | class Point(NamedTuple): 12 | x: int 13 | y: int 14 | 15 | 16 | class Size(NamedTuple): 17 | rows: int 18 | columns: int 19 | -------------------------------------------------------------------------------- /src/prompt_toolkit/enums.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from enum import Enum 4 | 5 | 6 | class EditingMode(Enum): 7 | # The set of key bindings that is active. 8 | VI = "VI" 9 | EMACS = "EMACS" 10 | 11 | 12 | #: Name of the search buffer. 13 | SEARCH_BUFFER = "SEARCH_BUFFER" 14 | 15 | #: Name of the default buffer. 16 | DEFAULT_BUFFER = "DEFAULT_BUFFER" 17 | 18 | #: Name of the system buffer. 19 | SYSTEM_BUFFER = "SYSTEM_BUFFER" 20 | -------------------------------------------------------------------------------- /src/prompt_toolkit/eventloop/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .async_generator import aclosing, generator_to_async_generator 4 | from .inputhook import ( 5 | InputHook, 6 | InputHookContext, 7 | InputHookSelector, 8 | new_eventloop_with_inputhook, 9 | set_eventloop_with_inputhook, 10 | ) 11 | from .utils import ( 12 | call_soon_threadsafe, 13 | get_traceback_from_context, 14 | run_in_executor_with_context, 15 | ) 16 | 17 | __all__ = [ 18 | # Async generator 19 | "generator_to_async_generator", 20 | "aclosing", 21 | # Utils. 22 | "run_in_executor_with_context", 23 | "call_soon_threadsafe", 24 | "get_traceback_from_context", 25 | # Inputhooks. 26 | "InputHook", 27 | "new_eventloop_with_inputhook", 28 | "set_eventloop_with_inputhook", 29 | "InputHookSelector", 30 | "InputHookContext", 31 | ] 32 | -------------------------------------------------------------------------------- /src/prompt_toolkit/eventloop/win32.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | 5 | assert sys.platform == "win32" 6 | 7 | from ctypes import pointer 8 | 9 | from ..utils import SPHINX_AUTODOC_RUNNING 10 | 11 | # Do not import win32-specific stuff when generating documentation. 12 | # Otherwise RTD would be unable to generate docs for this module. 13 | if not SPHINX_AUTODOC_RUNNING: 14 | from ctypes import windll 15 | 16 | from ctypes.wintypes import BOOL, DWORD, HANDLE 17 | 18 | from prompt_toolkit.win32_types import SECURITY_ATTRIBUTES 19 | 20 | __all__ = ["wait_for_handles", "create_win32_event"] 21 | 22 | 23 | WAIT_TIMEOUT = 0x00000102 24 | INFINITE = -1 25 | 26 | 27 | def wait_for_handles(handles: list[HANDLE], timeout: int = INFINITE) -> HANDLE | None: 28 | """ 29 | Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. 30 | Returns `None` on timeout. 31 | http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx 32 | 33 | Note that handles should be a list of `HANDLE` objects, not integers. See 34 | this comment in the patch by @quark-zju for the reason why: 35 | 36 | ''' Make sure HANDLE on Windows has a correct size 37 | 38 | Previously, the type of various HANDLEs are native Python integer 39 | types. The ctypes library will treat them as 4-byte integer when used 40 | in function arguments. On 64-bit Windows, HANDLE is 8-byte and usually 41 | a small integer. Depending on whether the extra 4 bytes are zero-ed out 42 | or not, things can happen to work, or break. ''' 43 | 44 | This function returns either `None` or one of the given `HANDLE` objects. 45 | (The return value can be tested with the `is` operator.) 46 | """ 47 | arrtype = HANDLE * len(handles) 48 | handle_array = arrtype(*handles) 49 | 50 | ret: int = windll.kernel32.WaitForMultipleObjects( 51 | len(handle_array), handle_array, BOOL(False), DWORD(timeout) 52 | ) 53 | 54 | if ret == WAIT_TIMEOUT: 55 | return None 56 | else: 57 | return handles[ret] 58 | 59 | 60 | def create_win32_event() -> HANDLE: 61 | """ 62 | Creates a Win32 unnamed Event . 63 | http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx 64 | """ 65 | return HANDLE( 66 | windll.kernel32.CreateEventA( 67 | pointer(SECURITY_ATTRIBUTES()), 68 | BOOL(True), # Manual reset event. 69 | BOOL(False), # Initial state. 70 | None, # Unnamed event object. 71 | ) 72 | ) 73 | -------------------------------------------------------------------------------- /src/prompt_toolkit/filters/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Filters decide whether something is active or not (they decide about a boolean 3 | state). This is used to enable/disable features, like key bindings, parts of 4 | the layout and other stuff. For instance, we could have a `HasSearch` filter 5 | attached to some part of the layout, in order to show that part of the user 6 | interface only while the user is searching. 7 | 8 | Filters are made to avoid having to attach callbacks to all event in order to 9 | propagate state. However, they are lazy, they don't automatically propagate the 10 | state of what they are observing. Only when a filter is called (it's actually a 11 | callable), it will calculate its value. So, its not really reactive 12 | programming, but it's made to fit for this framework. 13 | 14 | Filters can be chained using ``&`` and ``|`` operations, and inverted using the 15 | ``~`` operator, for instance:: 16 | 17 | filter = has_focus('default') & ~ has_selection 18 | """ 19 | 20 | from __future__ import annotations 21 | 22 | from .app import * 23 | from .base import Always, Condition, Filter, FilterOrBool, Never 24 | from .cli import * 25 | from .utils import is_true, to_filter 26 | 27 | __all__ = [ 28 | # app 29 | "has_arg", 30 | "has_completions", 31 | "completion_is_selected", 32 | "has_focus", 33 | "buffer_has_focus", 34 | "has_selection", 35 | "has_validation_error", 36 | "is_done", 37 | "is_read_only", 38 | "is_multiline", 39 | "renderer_height_is_known", 40 | "in_editing_mode", 41 | "in_paste_mode", 42 | "vi_mode", 43 | "vi_navigation_mode", 44 | "vi_insert_mode", 45 | "vi_insert_multiple_mode", 46 | "vi_replace_mode", 47 | "vi_selection_mode", 48 | "vi_waiting_for_text_object_mode", 49 | "vi_digraph_mode", 50 | "vi_recording_macro", 51 | "emacs_mode", 52 | "emacs_insert_mode", 53 | "emacs_selection_mode", 54 | "shift_selection_mode", 55 | "is_searching", 56 | "control_is_searchable", 57 | "vi_search_direction_reversed", 58 | # base. 59 | "Filter", 60 | "Never", 61 | "Always", 62 | "Condition", 63 | "FilterOrBool", 64 | # utils. 65 | "is_true", 66 | "to_filter", 67 | ] 68 | 69 | from .cli import __all__ as cli_all 70 | 71 | __all__.extend(cli_all) 72 | -------------------------------------------------------------------------------- /src/prompt_toolkit/filters/cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | For backwards-compatibility. keep this file. 3 | (Many people are going to have key bindings that rely on this file.) 4 | """ 5 | 6 | from __future__ import annotations 7 | 8 | from .app import * 9 | 10 | __all__ = [ 11 | # Old names. 12 | "HasArg", 13 | "HasCompletions", 14 | "HasFocus", 15 | "HasSelection", 16 | "HasValidationError", 17 | "IsDone", 18 | "IsReadOnly", 19 | "IsMultiline", 20 | "RendererHeightIsKnown", 21 | "InEditingMode", 22 | "InPasteMode", 23 | "ViMode", 24 | "ViNavigationMode", 25 | "ViInsertMode", 26 | "ViInsertMultipleMode", 27 | "ViReplaceMode", 28 | "ViSelectionMode", 29 | "ViWaitingForTextObjectMode", 30 | "ViDigraphMode", 31 | "EmacsMode", 32 | "EmacsInsertMode", 33 | "EmacsSelectionMode", 34 | "IsSearching", 35 | "HasSearch", 36 | "ControlIsSearchable", 37 | ] 38 | 39 | # Keep the original classnames for backwards compatibility. 40 | HasValidationError = lambda: has_validation_error 41 | HasArg = lambda: has_arg 42 | IsDone = lambda: is_done 43 | RendererHeightIsKnown = lambda: renderer_height_is_known 44 | ViNavigationMode = lambda: vi_navigation_mode 45 | InPasteMode = lambda: in_paste_mode 46 | EmacsMode = lambda: emacs_mode 47 | EmacsInsertMode = lambda: emacs_insert_mode 48 | ViMode = lambda: vi_mode 49 | IsSearching = lambda: is_searching 50 | HasSearch = lambda: is_searching 51 | ControlIsSearchable = lambda: control_is_searchable 52 | EmacsSelectionMode = lambda: emacs_selection_mode 53 | ViDigraphMode = lambda: vi_digraph_mode 54 | ViWaitingForTextObjectMode = lambda: vi_waiting_for_text_object_mode 55 | ViSelectionMode = lambda: vi_selection_mode 56 | ViReplaceMode = lambda: vi_replace_mode 57 | ViInsertMultipleMode = lambda: vi_insert_multiple_mode 58 | ViInsertMode = lambda: vi_insert_mode 59 | HasSelection = lambda: has_selection 60 | HasCompletions = lambda: has_completions 61 | IsReadOnly = lambda: is_read_only 62 | IsMultiline = lambda: is_multiline 63 | 64 | HasFocus = has_focus # No lambda here! (Has_focus is callable that returns a callable.) 65 | InEditingMode = in_editing_mode 66 | -------------------------------------------------------------------------------- /src/prompt_toolkit/filters/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .base import Always, Filter, FilterOrBool, Never 4 | 5 | __all__ = [ 6 | "to_filter", 7 | "is_true", 8 | ] 9 | 10 | 11 | _always = Always() 12 | _never = Never() 13 | 14 | 15 | _bool_to_filter: dict[bool, Filter] = { 16 | True: _always, 17 | False: _never, 18 | } 19 | 20 | 21 | def to_filter(bool_or_filter: FilterOrBool) -> Filter: 22 | """ 23 | Accept both booleans and Filters as input and 24 | turn it into a Filter. 25 | """ 26 | if isinstance(bool_or_filter, bool): 27 | return _bool_to_filter[bool_or_filter] 28 | 29 | if isinstance(bool_or_filter, Filter): 30 | return bool_or_filter 31 | 32 | raise TypeError(f"Expecting a bool or a Filter instance. Got {bool_or_filter!r}") 33 | 34 | 35 | def is_true(value: FilterOrBool) -> bool: 36 | """ 37 | Test whether `value` is True. In case of a Filter, call it. 38 | 39 | :param value: Boolean or `Filter` instance. 40 | """ 41 | return to_filter(value)() 42 | -------------------------------------------------------------------------------- /src/prompt_toolkit/formatted_text/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Many places in prompt_toolkit can take either plain text, or formatted text. 3 | For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either 4 | plain text or formatted text for the prompt. The 5 | :class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain 6 | text or formatted text. 7 | 8 | In any case, there is an input that can either be just plain text (a string), 9 | an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of 10 | `(style_string, text)` tuples. The :func:`.to_formatted_text` conversion 11 | function takes any of these and turns all of them into such a tuple sequence. 12 | """ 13 | 14 | from __future__ import annotations 15 | 16 | from .ansi import ANSI 17 | from .base import ( 18 | AnyFormattedText, 19 | FormattedText, 20 | OneStyleAndTextTuple, 21 | StyleAndTextTuples, 22 | Template, 23 | is_formatted_text, 24 | merge_formatted_text, 25 | to_formatted_text, 26 | ) 27 | from .html import HTML 28 | from .pygments import PygmentsTokens 29 | from .utils import ( 30 | fragment_list_len, 31 | fragment_list_to_text, 32 | fragment_list_width, 33 | split_lines, 34 | to_plain_text, 35 | ) 36 | 37 | __all__ = [ 38 | # Base. 39 | "AnyFormattedText", 40 | "OneStyleAndTextTuple", 41 | "to_formatted_text", 42 | "is_formatted_text", 43 | "Template", 44 | "merge_formatted_text", 45 | "FormattedText", 46 | "StyleAndTextTuples", 47 | # HTML. 48 | "HTML", 49 | # ANSI. 50 | "ANSI", 51 | # Pygments. 52 | "PygmentsTokens", 53 | # Utils. 54 | "fragment_list_len", 55 | "fragment_list_width", 56 | "fragment_list_to_text", 57 | "split_lines", 58 | "to_plain_text", 59 | ] 60 | -------------------------------------------------------------------------------- /src/prompt_toolkit/formatted_text/pygments.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | from prompt_toolkit.styles.pygments import pygments_token_to_classname 6 | 7 | from .base import StyleAndTextTuples 8 | 9 | if TYPE_CHECKING: 10 | from pygments.token import Token 11 | 12 | __all__ = [ 13 | "PygmentsTokens", 14 | ] 15 | 16 | 17 | class PygmentsTokens: 18 | """ 19 | Turn a pygments token list into a list of prompt_toolkit text fragments 20 | (``(style_str, text)`` tuples). 21 | """ 22 | 23 | def __init__(self, token_list: list[tuple[Token, str]]) -> None: 24 | self.token_list = token_list 25 | 26 | def __pt_formatted_text__(self) -> StyleAndTextTuples: 27 | result: StyleAndTextTuples = [] 28 | 29 | for token, text in self.token_list: 30 | result.append(("class:" + pygments_token_to_classname(token), text)) 31 | 32 | return result 33 | -------------------------------------------------------------------------------- /src/prompt_toolkit/input/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .base import DummyInput, Input, PipeInput 4 | from .defaults import create_input, create_pipe_input 5 | 6 | __all__ = [ 7 | # Base. 8 | "Input", 9 | "PipeInput", 10 | "DummyInput", 11 | # Defaults. 12 | "create_input", 13 | "create_pipe_input", 14 | ] 15 | -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .key_bindings import ( 4 | ConditionalKeyBindings, 5 | DynamicKeyBindings, 6 | KeyBindings, 7 | KeyBindingsBase, 8 | merge_key_bindings, 9 | ) 10 | from .key_processor import KeyPress, KeyPressEvent 11 | 12 | __all__ = [ 13 | # key_bindings. 14 | "ConditionalKeyBindings", 15 | "DynamicKeyBindings", 16 | "KeyBindings", 17 | "KeyBindingsBase", 18 | "merge_key_bindings", 19 | # key_processor 20 | "KeyPress", 21 | "KeyPressEvent", 22 | ] 23 | -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/bindings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/src/prompt_toolkit/key_binding/bindings/__init__.py -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/bindings/auto_suggest.py: -------------------------------------------------------------------------------- 1 | """ 2 | Key bindings for auto suggestion (for fish-style auto suggestion). 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import re 8 | 9 | from prompt_toolkit.application.current import get_app 10 | from prompt_toolkit.filters import Condition, emacs_mode 11 | from prompt_toolkit.key_binding.key_bindings import KeyBindings 12 | from prompt_toolkit.key_binding.key_processor import KeyPressEvent 13 | 14 | __all__ = [ 15 | "load_auto_suggest_bindings", 16 | ] 17 | 18 | E = KeyPressEvent 19 | 20 | 21 | def load_auto_suggest_bindings() -> KeyBindings: 22 | """ 23 | Key bindings for accepting auto suggestion text. 24 | 25 | (This has to come after the Vi bindings, because they also have an 26 | implementation for the "right arrow", but we really want the suggestion 27 | binding when a suggestion is available.) 28 | """ 29 | key_bindings = KeyBindings() 30 | handle = key_bindings.add 31 | 32 | @Condition 33 | def suggestion_available() -> bool: 34 | app = get_app() 35 | return ( 36 | app.current_buffer.suggestion is not None 37 | and len(app.current_buffer.suggestion.text) > 0 38 | and app.current_buffer.document.is_cursor_at_the_end 39 | ) 40 | 41 | @handle("c-f", filter=suggestion_available) 42 | @handle("c-e", filter=suggestion_available) 43 | @handle("right", filter=suggestion_available) 44 | def _accept(event: E) -> None: 45 | """ 46 | Accept suggestion. 47 | """ 48 | b = event.current_buffer 49 | suggestion = b.suggestion 50 | 51 | if suggestion: 52 | b.insert_text(suggestion.text) 53 | 54 | @handle("escape", "f", filter=suggestion_available & emacs_mode) 55 | def _fill(event: E) -> None: 56 | """ 57 | Fill partial suggestion. 58 | """ 59 | b = event.current_buffer 60 | suggestion = b.suggestion 61 | 62 | if suggestion: 63 | t = re.split(r"([^\s/]+(?:\s+|/))", suggestion.text) 64 | b.insert_text(next(x for x in t if x)) 65 | 66 | return key_bindings 67 | -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/bindings/cpr.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from prompt_toolkit.key_binding.key_processor import KeyPressEvent 4 | from prompt_toolkit.keys import Keys 5 | 6 | from ..key_bindings import KeyBindings 7 | 8 | __all__ = [ 9 | "load_cpr_bindings", 10 | ] 11 | 12 | E = KeyPressEvent 13 | 14 | 15 | def load_cpr_bindings() -> KeyBindings: 16 | key_bindings = KeyBindings() 17 | 18 | @key_bindings.add(Keys.CPRResponse, save_before=lambda e: False) 19 | def _(event: E) -> None: 20 | """ 21 | Handle incoming Cursor-Position-Request response. 22 | """ 23 | # The incoming data looks like u'\x1b[35;1R' 24 | # Parse row/col information. 25 | row, col = map(int, event.data[2:-1].split(";")) 26 | 27 | # Report absolute cursor position to the renderer. 28 | event.app.renderer.report_absolute_cursor_row(row) 29 | 30 | return key_bindings 31 | -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/bindings/focus.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from prompt_toolkit.key_binding.key_processor import KeyPressEvent 4 | 5 | __all__ = [ 6 | "focus_next", 7 | "focus_previous", 8 | ] 9 | 10 | E = KeyPressEvent 11 | 12 | 13 | def focus_next(event: E) -> None: 14 | """ 15 | Focus the next visible Window. 16 | (Often bound to the `Tab` key.) 17 | """ 18 | event.app.layout.focus_next() 19 | 20 | 21 | def focus_previous(event: E) -> None: 22 | """ 23 | Focus the previous visible Window. 24 | (Often bound to the `BackTab` key.) 25 | """ 26 | event.app.layout.focus_previous() 27 | -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/bindings/open_in_editor.py: -------------------------------------------------------------------------------- 1 | """ 2 | Open in editor key bindings. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | from prompt_toolkit.filters import emacs_mode, has_selection, vi_navigation_mode 8 | 9 | from ..key_bindings import KeyBindings, KeyBindingsBase, merge_key_bindings 10 | from .named_commands import get_by_name 11 | 12 | __all__ = [ 13 | "load_open_in_editor_bindings", 14 | "load_emacs_open_in_editor_bindings", 15 | "load_vi_open_in_editor_bindings", 16 | ] 17 | 18 | 19 | def load_open_in_editor_bindings() -> KeyBindingsBase: 20 | """ 21 | Load both the Vi and emacs key bindings for handling edit-and-execute-command. 22 | """ 23 | return merge_key_bindings( 24 | [ 25 | load_emacs_open_in_editor_bindings(), 26 | load_vi_open_in_editor_bindings(), 27 | ] 28 | ) 29 | 30 | 31 | def load_emacs_open_in_editor_bindings() -> KeyBindings: 32 | """ 33 | Pressing C-X C-E will open the buffer in an external editor. 34 | """ 35 | key_bindings = KeyBindings() 36 | 37 | key_bindings.add("c-x", "c-e", filter=emacs_mode & ~has_selection)( 38 | get_by_name("edit-and-execute-command") 39 | ) 40 | 41 | return key_bindings 42 | 43 | 44 | def load_vi_open_in_editor_bindings() -> KeyBindings: 45 | """ 46 | Pressing 'v' in navigation mode will open the buffer in an external editor. 47 | """ 48 | key_bindings = KeyBindings() 49 | key_bindings.add("v", filter=vi_navigation_mode)( 50 | get_by_name("edit-and-execute-command") 51 | ) 52 | return key_bindings 53 | -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/defaults.py: -------------------------------------------------------------------------------- 1 | """ 2 | Default key bindings.:: 3 | 4 | key_bindings = load_key_bindings() 5 | app = Application(key_bindings=key_bindings) 6 | """ 7 | 8 | from __future__ import annotations 9 | 10 | from prompt_toolkit.filters import buffer_has_focus 11 | from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings 12 | from prompt_toolkit.key_binding.bindings.cpr import load_cpr_bindings 13 | from prompt_toolkit.key_binding.bindings.emacs import ( 14 | load_emacs_bindings, 15 | load_emacs_search_bindings, 16 | load_emacs_shift_selection_bindings, 17 | ) 18 | from prompt_toolkit.key_binding.bindings.mouse import load_mouse_bindings 19 | from prompt_toolkit.key_binding.bindings.vi import ( 20 | load_vi_bindings, 21 | load_vi_search_bindings, 22 | ) 23 | from prompt_toolkit.key_binding.key_bindings import ( 24 | ConditionalKeyBindings, 25 | KeyBindingsBase, 26 | merge_key_bindings, 27 | ) 28 | 29 | __all__ = [ 30 | "load_key_bindings", 31 | ] 32 | 33 | 34 | def load_key_bindings() -> KeyBindingsBase: 35 | """ 36 | Create a KeyBindings object that contains the default key bindings. 37 | """ 38 | all_bindings = merge_key_bindings( 39 | [ 40 | # Load basic bindings. 41 | load_basic_bindings(), 42 | # Load emacs bindings. 43 | load_emacs_bindings(), 44 | load_emacs_search_bindings(), 45 | load_emacs_shift_selection_bindings(), 46 | # Load Vi bindings. 47 | load_vi_bindings(), 48 | load_vi_search_bindings(), 49 | ] 50 | ) 51 | 52 | return merge_key_bindings( 53 | [ 54 | # Make sure that the above key bindings are only active if the 55 | # currently focused control is a `BufferControl`. For other controls, we 56 | # don't want these key bindings to intervene. (This would break "ptterm" 57 | # for instance, which handles 'Keys.Any' in the user control itself.) 58 | ConditionalKeyBindings(all_bindings, buffer_has_focus), 59 | # Active, even when no buffer has been focused. 60 | load_mouse_bindings(), 61 | load_cpr_bindings(), 62 | ] 63 | ) 64 | -------------------------------------------------------------------------------- /src/prompt_toolkit/key_binding/emacs_state.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .key_processor import KeyPress 4 | 5 | __all__ = [ 6 | "EmacsState", 7 | ] 8 | 9 | 10 | class EmacsState: 11 | """ 12 | Mutable class to hold Emacs specific state. 13 | """ 14 | 15 | def __init__(self) -> None: 16 | # Simple macro recording. (Like Readline does.) 17 | # (For Emacs mode.) 18 | self.macro: list[KeyPress] | None = [] 19 | self.current_recording: list[KeyPress] | None = None 20 | 21 | def reset(self) -> None: 22 | self.current_recording = None 23 | 24 | @property 25 | def is_recording(self) -> bool: 26 | "Tell whether we are recording a macro." 27 | return self.current_recording is not None 28 | 29 | def start_macro(self) -> None: 30 | "Start recording macro." 31 | self.current_recording = [] 32 | 33 | def end_macro(self) -> None: 34 | "End recording macro." 35 | self.macro = self.current_recording 36 | self.current_recording = None 37 | -------------------------------------------------------------------------------- /src/prompt_toolkit/layout/dummy.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dummy layout. Used when somebody creates an `Application` without specifying a 3 | `Layout`. 4 | """ 5 | 6 | from __future__ import annotations 7 | 8 | from prompt_toolkit.formatted_text import HTML 9 | from prompt_toolkit.key_binding import KeyBindings 10 | from prompt_toolkit.key_binding.key_processor import KeyPressEvent 11 | 12 | from .containers import Window 13 | from .controls import FormattedTextControl 14 | from .dimension import D 15 | from .layout import Layout 16 | 17 | __all__ = [ 18 | "create_dummy_layout", 19 | ] 20 | 21 | E = KeyPressEvent 22 | 23 | 24 | def create_dummy_layout() -> Layout: 25 | """ 26 | Create a dummy layout for use in an 'Application' that doesn't have a 27 | layout specified. When ENTER is pressed, the application quits. 28 | """ 29 | kb = KeyBindings() 30 | 31 | @kb.add("enter") 32 | def enter(event: E) -> None: 33 | event.app.exit() 34 | 35 | control = FormattedTextControl( 36 | HTML("No layout specified. Press ENTER to quit."), 37 | key_bindings=kb, 38 | ) 39 | window = Window(content=control, height=D(min=1)) 40 | return Layout(container=window, focused_element=window) 41 | -------------------------------------------------------------------------------- /src/prompt_toolkit/layout/mouse_handlers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from collections import defaultdict 4 | from typing import TYPE_CHECKING, Callable 5 | 6 | from prompt_toolkit.mouse_events import MouseEvent 7 | 8 | if TYPE_CHECKING: 9 | from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone 10 | 11 | __all__ = [ 12 | "MouseHandler", 13 | "MouseHandlers", 14 | ] 15 | 16 | 17 | MouseHandler = Callable[[MouseEvent], "NotImplementedOrNone"] 18 | 19 | 20 | class MouseHandlers: 21 | """ 22 | Two dimensional raster of callbacks for mouse events. 23 | """ 24 | 25 | def __init__(self) -> None: 26 | def dummy_callback(mouse_event: MouseEvent) -> NotImplementedOrNone: 27 | """ 28 | :param mouse_event: `MouseEvent` instance. 29 | """ 30 | return NotImplemented 31 | 32 | # NOTE: Previously, the data structure was a dictionary mapping (x,y) 33 | # to the handlers. This however would be more inefficient when copying 34 | # over the mouse handlers of the visible region in the scrollable pane. 35 | 36 | # Map y (row) to x (column) to handlers. 37 | self.mouse_handlers: defaultdict[int, defaultdict[int, MouseHandler]] = ( 38 | defaultdict(lambda: defaultdict(lambda: dummy_callback)) 39 | ) 40 | 41 | def set_mouse_handler_for_range( 42 | self, 43 | x_min: int, 44 | x_max: int, 45 | y_min: int, 46 | y_max: int, 47 | handler: Callable[[MouseEvent], NotImplementedOrNone], 48 | ) -> None: 49 | """ 50 | Set mouse handler for a region. 51 | """ 52 | for y in range(y_min, y_max): 53 | row = self.mouse_handlers[y] 54 | 55 | for x in range(x_min, x_max): 56 | row[x] = handler 57 | -------------------------------------------------------------------------------- /src/prompt_toolkit/layout/utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING, Iterable, List, TypeVar, cast, overload 4 | 5 | from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple 6 | 7 | if TYPE_CHECKING: 8 | from typing_extensions import SupportsIndex 9 | 10 | __all__ = [ 11 | "explode_text_fragments", 12 | ] 13 | 14 | _T = TypeVar("_T", bound=OneStyleAndTextTuple) 15 | 16 | 17 | class _ExplodedList(List[_T]): 18 | """ 19 | Wrapper around a list, that marks it as 'exploded'. 20 | 21 | As soon as items are added or the list is extended, the new items are 22 | automatically exploded as well. 23 | """ 24 | 25 | exploded = True 26 | 27 | def append(self, item: _T) -> None: 28 | self.extend([item]) 29 | 30 | def extend(self, lst: Iterable[_T]) -> None: 31 | super().extend(explode_text_fragments(lst)) 32 | 33 | def insert(self, index: SupportsIndex, item: _T) -> None: 34 | raise NotImplementedError # TODO 35 | 36 | # TODO: When creating a copy() or [:], return also an _ExplodedList. 37 | 38 | @overload 39 | def __setitem__(self, index: SupportsIndex, value: _T) -> None: ... 40 | 41 | @overload 42 | def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ... 43 | 44 | def __setitem__( 45 | self, index: SupportsIndex | slice, value: _T | Iterable[_T] 46 | ) -> None: 47 | """ 48 | Ensure that when `(style_str, 'long string')` is set, the string will be 49 | exploded. 50 | """ 51 | if not isinstance(index, slice): 52 | int_index = index.__index__() 53 | index = slice(int_index, int_index + 1) 54 | if isinstance(value, tuple): # In case of `OneStyleAndTextTuple`. 55 | value = cast("List[_T]", [value]) 56 | 57 | super().__setitem__(index, explode_text_fragments(value)) 58 | 59 | 60 | def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]: 61 | """ 62 | Turn a list of (style_str, text) tuples into another list where each string is 63 | exactly one character. 64 | 65 | It should be fine to call this function several times. Calling this on a 66 | list that is already exploded, is a null operation. 67 | 68 | :param fragments: List of (style, text) tuples. 69 | """ 70 | # When the fragments is already exploded, don't explode again. 71 | if isinstance(fragments, _ExplodedList): 72 | return fragments 73 | 74 | result: list[_T] = [] 75 | 76 | for style, string, *rest in fragments: 77 | for c in string: 78 | result.append((style, c, *rest)) # type: ignore 79 | 80 | return _ExplodedList(result) 81 | -------------------------------------------------------------------------------- /src/prompt_toolkit/lexers/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Lexer interface and implementations. 3 | Used for syntax highlighting. 4 | """ 5 | 6 | from __future__ import annotations 7 | 8 | from .base import DynamicLexer, Lexer, SimpleLexer 9 | from .pygments import PygmentsLexer, RegexSync, SyncFromStart, SyntaxSync 10 | 11 | __all__ = [ 12 | # Base. 13 | "Lexer", 14 | "SimpleLexer", 15 | "DynamicLexer", 16 | # Pygments. 17 | "PygmentsLexer", 18 | "RegexSync", 19 | "SyncFromStart", 20 | "SyntaxSync", 21 | ] 22 | -------------------------------------------------------------------------------- /src/prompt_toolkit/log.py: -------------------------------------------------------------------------------- 1 | """ 2 | Logging configuration. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | import logging 8 | 9 | __all__ = [ 10 | "logger", 11 | ] 12 | 13 | logger = logging.getLogger(__package__) 14 | -------------------------------------------------------------------------------- /src/prompt_toolkit/output/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .base import DummyOutput, Output 4 | from .color_depth import ColorDepth 5 | from .defaults import create_output 6 | 7 | __all__ = [ 8 | # Base. 9 | "Output", 10 | "DummyOutput", 11 | # Color depth. 12 | "ColorDepth", 13 | # Defaults. 14 | "create_output", 15 | ] 16 | -------------------------------------------------------------------------------- /src/prompt_toolkit/output/color_depth.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from enum import Enum 5 | 6 | __all__ = [ 7 | "ColorDepth", 8 | ] 9 | 10 | 11 | class ColorDepth(str, Enum): 12 | """ 13 | Possible color depth values for the output. 14 | """ 15 | 16 | value: str 17 | 18 | #: One color only. 19 | DEPTH_1_BIT = "DEPTH_1_BIT" 20 | 21 | #: ANSI Colors. 22 | DEPTH_4_BIT = "DEPTH_4_BIT" 23 | 24 | #: The default. 25 | DEPTH_8_BIT = "DEPTH_8_BIT" 26 | 27 | #: 24 bit True color. 28 | DEPTH_24_BIT = "DEPTH_24_BIT" 29 | 30 | # Aliases. 31 | MONOCHROME = DEPTH_1_BIT 32 | ANSI_COLORS_ONLY = DEPTH_4_BIT 33 | DEFAULT = DEPTH_8_BIT 34 | TRUE_COLOR = DEPTH_24_BIT 35 | 36 | @classmethod 37 | def from_env(cls) -> ColorDepth | None: 38 | """ 39 | Return the color depth if the $PROMPT_TOOLKIT_COLOR_DEPTH environment 40 | variable has been set. 41 | 42 | This is a way to enforce a certain color depth in all prompt_toolkit 43 | applications. 44 | """ 45 | # Disable color if a `NO_COLOR` environment variable is set. 46 | # See: https://no-color.org/ 47 | if os.environ.get("NO_COLOR"): 48 | return cls.DEPTH_1_BIT 49 | 50 | # Check the `PROMPT_TOOLKIT_COLOR_DEPTH` environment variable. 51 | all_values = [i.value for i in ColorDepth] 52 | if os.environ.get("PROMPT_TOOLKIT_COLOR_DEPTH") in all_values: 53 | return cls(os.environ["PROMPT_TOOLKIT_COLOR_DEPTH"]) 54 | 55 | return None 56 | 57 | @classmethod 58 | def default(cls) -> ColorDepth: 59 | """ 60 | Return the default color depth for the default output. 61 | """ 62 | from .defaults import create_output 63 | 64 | return create_output().get_default_color_depth() 65 | -------------------------------------------------------------------------------- /src/prompt_toolkit/output/conemu.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import sys 4 | 5 | assert sys.platform == "win32" 6 | 7 | from typing import Any, TextIO 8 | 9 | from prompt_toolkit.data_structures import Size 10 | 11 | from .base import Output 12 | from .color_depth import ColorDepth 13 | from .vt100 import Vt100_Output 14 | from .win32 import Win32Output 15 | 16 | __all__ = [ 17 | "ConEmuOutput", 18 | ] 19 | 20 | 21 | class ConEmuOutput: 22 | """ 23 | ConEmu (Windows) output abstraction. 24 | 25 | ConEmu is a Windows console application, but it also supports ANSI escape 26 | sequences. This output class is actually a proxy to both `Win32Output` and 27 | `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but 28 | all cursor movements and scrolling happens through the `Vt100_Output`. 29 | 30 | This way, we can have 256 colors in ConEmu and Cmder. Rendering will be 31 | even a little faster as well. 32 | 33 | http://conemu.github.io/ 34 | http://gooseberrycreative.com/cmder/ 35 | """ 36 | 37 | def __init__( 38 | self, stdout: TextIO, default_color_depth: ColorDepth | None = None 39 | ) -> None: 40 | self.win32_output = Win32Output(stdout, default_color_depth=default_color_depth) 41 | self.vt100_output = Vt100_Output( 42 | stdout, lambda: Size(0, 0), default_color_depth=default_color_depth 43 | ) 44 | 45 | @property 46 | def responds_to_cpr(self) -> bool: 47 | return False # We don't need this on Windows. 48 | 49 | def __getattr__(self, name: str) -> Any: 50 | if name in ( 51 | "get_size", 52 | "get_rows_below_cursor_position", 53 | "enable_mouse_support", 54 | "disable_mouse_support", 55 | "scroll_buffer_to_prompt", 56 | "get_win32_screen_buffer_info", 57 | "enable_bracketed_paste", 58 | "disable_bracketed_paste", 59 | ): 60 | return getattr(self.win32_output, name) 61 | else: 62 | return getattr(self.vt100_output, name) 63 | 64 | 65 | Output.register(ConEmuOutput) 66 | -------------------------------------------------------------------------------- /src/prompt_toolkit/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/prompt-toolkit/python-prompt-toolkit/d997aab538e434a6ca07d6bee226fd5b0628262f/src/prompt_toolkit/py.typed -------------------------------------------------------------------------------- /src/prompt_toolkit/selection.py: -------------------------------------------------------------------------------- 1 | """ 2 | Data structures for the selection. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | from enum import Enum 8 | 9 | __all__ = [ 10 | "SelectionType", 11 | "PasteMode", 12 | "SelectionState", 13 | ] 14 | 15 | 16 | class SelectionType(Enum): 17 | """ 18 | Type of selection. 19 | """ 20 | 21 | #: Characters. (Visual in Vi.) 22 | CHARACTERS = "CHARACTERS" 23 | 24 | #: Whole lines. (Visual-Line in Vi.) 25 | LINES = "LINES" 26 | 27 | #: A block selection. (Visual-Block in Vi.) 28 | BLOCK = "BLOCK" 29 | 30 | 31 | class PasteMode(Enum): 32 | EMACS = "EMACS" # Yank like emacs. 33 | VI_AFTER = "VI_AFTER" # When pressing 'p' in Vi. 34 | VI_BEFORE = "VI_BEFORE" # When pressing 'P' in Vi. 35 | 36 | 37 | class SelectionState: 38 | """ 39 | State of the current selection. 40 | 41 | :param original_cursor_position: int 42 | :param type: :class:`~.SelectionType` 43 | """ 44 | 45 | def __init__( 46 | self, 47 | original_cursor_position: int = 0, 48 | type: SelectionType = SelectionType.CHARACTERS, 49 | ) -> None: 50 | self.original_cursor_position = original_cursor_position 51 | self.type = type 52 | self.shift_mode = False 53 | 54 | def enter_shift_mode(self) -> None: 55 | self.shift_mode = True 56 | 57 | def __repr__(self) -> str: 58 | return f"{self.__class__.__name__}(original_cursor_position={self.original_cursor_position!r}, type={self.type!r})" 59 | -------------------------------------------------------------------------------- /src/prompt_toolkit/shortcuts/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .dialogs import ( 4 | button_dialog, 5 | checkboxlist_dialog, 6 | input_dialog, 7 | message_dialog, 8 | progress_dialog, 9 | radiolist_dialog, 10 | yes_no_dialog, 11 | ) 12 | from .progress_bar import ProgressBar, ProgressBarCounter 13 | from .prompt import ( 14 | CompleteStyle, 15 | PromptSession, 16 | confirm, 17 | create_confirm_session, 18 | prompt, 19 | ) 20 | from .utils import clear, clear_title, print_container, print_formatted_text, set_title 21 | 22 | __all__ = [ 23 | # Dialogs. 24 | "input_dialog", 25 | "message_dialog", 26 | "progress_dialog", 27 | "checkboxlist_dialog", 28 | "radiolist_dialog", 29 | "yes_no_dialog", 30 | "button_dialog", 31 | # Prompts. 32 | "PromptSession", 33 | "prompt", 34 | "confirm", 35 | "create_confirm_session", 36 | "CompleteStyle", 37 | # Progress bars. 38 | "ProgressBar", 39 | "ProgressBarCounter", 40 | # Utils. 41 | "clear", 42 | "clear_title", 43 | "print_container", 44 | "print_formatted_text", 45 | "set_title", 46 | ] 47 | -------------------------------------------------------------------------------- /src/prompt_toolkit/shortcuts/progress_bar/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from .base import ProgressBar, ProgressBarCounter 4 | from .formatters import ( 5 | Bar, 6 | Formatter, 7 | IterationsPerSecond, 8 | Label, 9 | Percentage, 10 | Progress, 11 | Rainbow, 12 | SpinningWheel, 13 | Text, 14 | TimeElapsed, 15 | TimeLeft, 16 | ) 17 | 18 | __all__ = [ 19 | "ProgressBar", 20 | "ProgressBarCounter", 21 | # Formatters. 22 | "Formatter", 23 | "Text", 24 | "Label", 25 | "Percentage", 26 | "Bar", 27 | "Progress", 28 | "TimeElapsed", 29 | "TimeLeft", 30 | "IterationsPerSecond", 31 | "SpinningWheel", 32 | "Rainbow", 33 | ] 34 | -------------------------------------------------------------------------------- /src/prompt_toolkit/styles/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Styling for prompt_toolkit applications. 3 | """ 4 | 5 | from __future__ import annotations 6 | 7 | from .base import ( 8 | ANSI_COLOR_NAMES, 9 | DEFAULT_ATTRS, 10 | Attrs, 11 | BaseStyle, 12 | DummyStyle, 13 | DynamicStyle, 14 | ) 15 | from .defaults import default_pygments_style, default_ui_style 16 | from .named_colors import NAMED_COLORS 17 | from .pygments import ( 18 | pygments_token_to_classname, 19 | style_from_pygments_cls, 20 | style_from_pygments_dict, 21 | ) 22 | from .style import Priority, Style, merge_styles, parse_color 23 | from .style_transformation import ( 24 | AdjustBrightnessStyleTransformation, 25 | ConditionalStyleTransformation, 26 | DummyStyleTransformation, 27 | DynamicStyleTransformation, 28 | ReverseStyleTransformation, 29 | SetDefaultColorStyleTransformation, 30 | StyleTransformation, 31 | SwapLightAndDarkStyleTransformation, 32 | merge_style_transformations, 33 | ) 34 | 35 | __all__ = [ 36 | # Base. 37 | "Attrs", 38 | "DEFAULT_ATTRS", 39 | "ANSI_COLOR_NAMES", 40 | "BaseStyle", 41 | "DummyStyle", 42 | "DynamicStyle", 43 | # Defaults. 44 | "default_ui_style", 45 | "default_pygments_style", 46 | # Style. 47 | "Style", 48 | "Priority", 49 | "merge_styles", 50 | "parse_color", 51 | # Style transformation. 52 | "StyleTransformation", 53 | "SwapLightAndDarkStyleTransformation", 54 | "ReverseStyleTransformation", 55 | "SetDefaultColorStyleTransformation", 56 | "AdjustBrightnessStyleTransformation", 57 | "DummyStyleTransformation", 58 | "ConditionalStyleTransformation", 59 | "DynamicStyleTransformation", 60 | "merge_style_transformations", 61 | # Pygments. 62 | "style_from_pygments_cls", 63 | "style_from_pygments_dict", 64 | "pygments_token_to_classname", 65 | # Named colors. 66 | "NAMED_COLORS", 67 | ] 68 | -------------------------------------------------------------------------------- /src/prompt_toolkit/styles/pygments.py: -------------------------------------------------------------------------------- 1 | """ 2 | Adaptor for building prompt_toolkit styles, starting from a Pygments style. 3 | 4 | Usage:: 5 | 6 | from pygments.styles.tango import TangoStyle 7 | style = style_from_pygments_cls(pygments_style_cls=TangoStyle) 8 | """ 9 | 10 | from __future__ import annotations 11 | 12 | from typing import TYPE_CHECKING 13 | 14 | from .style import Style 15 | 16 | if TYPE_CHECKING: 17 | from pygments.style import Style as PygmentsStyle 18 | from pygments.token import Token 19 | 20 | 21 | __all__ = [ 22 | "style_from_pygments_cls", 23 | "style_from_pygments_dict", 24 | "pygments_token_to_classname", 25 | ] 26 | 27 | 28 | def style_from_pygments_cls(pygments_style_cls: type[PygmentsStyle]) -> Style: 29 | """ 30 | Shortcut to create a :class:`.Style` instance from a Pygments style class 31 | and a style dictionary. 32 | 33 | Example:: 34 | 35 | from prompt_toolkit.styles.from_pygments import style_from_pygments_cls 36 | from pygments.styles import get_style_by_name 37 | style = style_from_pygments_cls(get_style_by_name('monokai')) 38 | 39 | :param pygments_style_cls: Pygments style class to start from. 40 | """ 41 | # Import inline. 42 | from pygments.style import Style as PygmentsStyle 43 | 44 | assert issubclass(pygments_style_cls, PygmentsStyle) 45 | 46 | return style_from_pygments_dict(pygments_style_cls.styles) 47 | 48 | 49 | def style_from_pygments_dict(pygments_dict: dict[Token, str]) -> Style: 50 | """ 51 | Create a :class:`.Style` instance from a Pygments style dictionary. 52 | (One that maps Token objects to style strings.) 53 | """ 54 | pygments_style = [] 55 | 56 | for token, style in pygments_dict.items(): 57 | pygments_style.append((pygments_token_to_classname(token), style)) 58 | 59 | return Style(pygments_style) 60 | 61 | 62 | def pygments_token_to_classname(token: Token) -> str: 63 | """ 64 | Turn e.g. `Token.Name.Exception` into `'pygments.name.exception'`. 65 | 66 | (Our Pygments lexer will also turn the tokens that pygments produces in a 67 | prompt_toolkit list of fragments that match these styling rules.) 68 | """ 69 | parts = ("pygments",) + token 70 | return ".".join(parts).lower() 71 | -------------------------------------------------------------------------------- /src/prompt_toolkit/token.py: -------------------------------------------------------------------------------- 1 | """ """ 2 | 3 | from __future__ import annotations 4 | 5 | __all__ = [ 6 | "ZeroWidthEscape", 7 | ] 8 | 9 | ZeroWidthEscape = "[ZeroWidthEscape]" 10 | -------------------------------------------------------------------------------- /src/prompt_toolkit/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Collection of reusable components for building full screen applications. 3 | These are higher level abstractions on top of the `prompt_toolkit.layout` 4 | module. 5 | 6 | Most of these widgets implement the ``__pt_container__`` method, which makes it 7 | possible to embed these in the layout like any other container. 8 | """ 9 | 10 | from __future__ import annotations 11 | 12 | from .base import ( 13 | Box, 14 | Button, 15 | Checkbox, 16 | CheckboxList, 17 | Frame, 18 | HorizontalLine, 19 | Label, 20 | ProgressBar, 21 | RadioList, 22 | Shadow, 23 | TextArea, 24 | VerticalLine, 25 | ) 26 | from .dialogs import Dialog 27 | from .menus import MenuContainer, MenuItem 28 | from .toolbars import ( 29 | ArgToolbar, 30 | CompletionsToolbar, 31 | FormattedTextToolbar, 32 | SearchToolbar, 33 | SystemToolbar, 34 | ValidationToolbar, 35 | ) 36 | 37 | __all__ = [ 38 | # Base. 39 | "TextArea", 40 | "Label", 41 | "Button", 42 | "Frame", 43 | "Shadow", 44 | "Box", 45 | "VerticalLine", 46 | "HorizontalLine", 47 | "CheckboxList", 48 | "RadioList", 49 | "Checkbox", 50 | "ProgressBar", 51 | # Toolbars. 52 | "ArgToolbar", 53 | "CompletionsToolbar", 54 | "FormattedTextToolbar", 55 | "SearchToolbar", 56 | "SystemToolbar", 57 | "ValidationToolbar", 58 | # Dialogs. 59 | "Dialog", 60 | # Menus. 61 | "MenuContainer", 62 | "MenuItem", 63 | ] 64 | -------------------------------------------------------------------------------- /tests/test_async_generator.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from asyncio import run 4 | 5 | from prompt_toolkit.eventloop import generator_to_async_generator 6 | 7 | 8 | def _sync_generator(): 9 | yield 1 10 | yield 10 11 | 12 | 13 | def test_generator_to_async_generator(): 14 | """ 15 | Test conversion of sync to async generator. 16 | This should run the synchronous parts in a background thread. 17 | """ 18 | async_gen = generator_to_async_generator(_sync_generator) 19 | 20 | items = [] 21 | 22 | async def consume_async_generator(): 23 | async for item in async_gen: 24 | items.append(item) 25 | 26 | # Run the event loop until all items are collected. 27 | run(consume_async_generator()) 28 | assert items == [1, 10] 29 | -------------------------------------------------------------------------------- /tests/test_document.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from prompt_toolkit.document import Document 6 | 7 | 8 | @pytest.fixture 9 | def document(): 10 | return Document( 11 | "line 1\n" + "line 2\n" + "line 3\n" + "line 4\n", len("line 1\n" + "lin") 12 | ) 13 | 14 | 15 | def test_current_char(document): 16 | assert document.current_char == "e" 17 | assert document.char_before_cursor == "n" 18 | 19 | 20 | def test_text_before_cursor(document): 21 | assert document.text_before_cursor == "line 1\nlin" 22 | 23 | 24 | def test_text_after_cursor(document): 25 | assert document.text_after_cursor == "e 2\n" + "line 3\n" + "line 4\n" 26 | 27 | 28 | def test_lines(document): 29 | assert document.lines == ["line 1", "line 2", "line 3", "line 4", ""] 30 | 31 | 32 | def test_line_count(document): 33 | assert document.line_count == 5 34 | 35 | 36 | def test_current_line_before_cursor(document): 37 | assert document.current_line_before_cursor == "lin" 38 | 39 | 40 | def test_current_line_after_cursor(document): 41 | assert document.current_line_after_cursor == "e 2" 42 | 43 | 44 | def test_current_line(document): 45 | assert document.current_line == "line 2" 46 | 47 | 48 | def test_cursor_position(document): 49 | assert document.cursor_position_row == 1 50 | assert document.cursor_position_col == 3 51 | 52 | d = Document("", 0) 53 | assert d.cursor_position_row == 0 54 | assert d.cursor_position_col == 0 55 | 56 | 57 | def test_translate_index_to_position(document): 58 | pos = document.translate_index_to_position(len("line 1\nline 2\nlin")) 59 | 60 | assert pos[0] == 2 61 | assert pos[1] == 3 62 | 63 | pos = document.translate_index_to_position(0) 64 | assert pos == (0, 0) 65 | 66 | 67 | def test_is_cursor_at_the_end(document): 68 | assert Document("hello", 5).is_cursor_at_the_end 69 | assert not Document("hello", 4).is_cursor_at_the_end 70 | -------------------------------------------------------------------------------- /tests/test_layout.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from prompt_toolkit.layout import InvalidLayoutError, Layout 6 | from prompt_toolkit.layout.containers import HSplit, VSplit, Window 7 | from prompt_toolkit.layout.controls import BufferControl 8 | 9 | 10 | def test_layout_class(): 11 | c1 = BufferControl() 12 | c2 = BufferControl() 13 | c3 = BufferControl() 14 | win1 = Window(content=c1) 15 | win2 = Window(content=c2) 16 | win3 = Window(content=c3) 17 | 18 | layout = Layout(container=VSplit([HSplit([win1, win2]), win3])) 19 | 20 | # Listing of windows/controls. 21 | assert list(layout.find_all_windows()) == [win1, win2, win3] 22 | assert list(layout.find_all_controls()) == [c1, c2, c3] 23 | 24 | # Focusing something. 25 | layout.focus(c1) 26 | assert layout.has_focus(c1) 27 | assert layout.has_focus(win1) 28 | assert layout.current_control == c1 29 | assert layout.previous_control == c1 30 | 31 | layout.focus(c2) 32 | assert layout.has_focus(c2) 33 | assert layout.has_focus(win2) 34 | assert layout.current_control == c2 35 | assert layout.previous_control == c1 36 | 37 | layout.focus(win3) 38 | assert layout.has_focus(c3) 39 | assert layout.has_focus(win3) 40 | assert layout.current_control == c3 41 | assert layout.previous_control == c2 42 | 43 | # Pop focus. This should focus the previous control again. 44 | layout.focus_last() 45 | assert layout.has_focus(c2) 46 | assert layout.has_focus(win2) 47 | assert layout.current_control == c2 48 | assert layout.previous_control == c1 49 | 50 | 51 | def test_create_invalid_layout(): 52 | with pytest.raises(InvalidLayoutError): 53 | Layout(HSplit([])) 54 | -------------------------------------------------------------------------------- /tests/test_memory_leaks.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import gc 4 | 5 | import pytest 6 | 7 | from prompt_toolkit.shortcuts.prompt import PromptSession 8 | 9 | 10 | def _count_prompt_session_instances() -> int: 11 | # Run full GC collection first. 12 | gc.collect() 13 | 14 | # Count number of remaining referenced `PromptSession` instances. 15 | objects = gc.get_objects() 16 | return len([obj for obj in objects if isinstance(obj, PromptSession)]) 17 | 18 | 19 | # Fails in GitHub CI, probably due to GC differences. 20 | @pytest.mark.xfail(reason="Memory leak testing fails in GitHub CI.") 21 | def test_prompt_session_memory_leak() -> None: 22 | before_count = _count_prompt_session_instances() 23 | 24 | # Somehow in CI/CD, the before_count is > 0 25 | assert before_count == 0 26 | 27 | p = PromptSession() 28 | 29 | after_count = _count_prompt_session_instances() 30 | assert after_count == before_count + 1 31 | 32 | del p 33 | 34 | after_delete_count = _count_prompt_session_instances() 35 | assert after_delete_count == before_count 36 | -------------------------------------------------------------------------------- /tests/test_shortcuts.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from prompt_toolkit.shortcuts import print_container 4 | from prompt_toolkit.shortcuts.prompt import _split_multiline_prompt 5 | from prompt_toolkit.widgets import Frame, TextArea 6 | 7 | 8 | def test_split_multiline_prompt(): 9 | # Test 1: no newlines: 10 | tokens = [("class:testclass", "ab")] 11 | has_before_tokens, before, first_input_line = _split_multiline_prompt( 12 | lambda: tokens 13 | ) 14 | assert has_before_tokens() is False 15 | assert before() == [] 16 | assert first_input_line() == [ 17 | ("class:testclass", "a"), 18 | ("class:testclass", "b"), 19 | ] 20 | 21 | # Test 1: multiple lines. 22 | tokens = [("class:testclass", "ab\ncd\nef")] 23 | has_before_tokens, before, first_input_line = _split_multiline_prompt( 24 | lambda: tokens 25 | ) 26 | assert has_before_tokens() is True 27 | assert before() == [ 28 | ("class:testclass", "a"), 29 | ("class:testclass", "b"), 30 | ("class:testclass", "\n"), 31 | ("class:testclass", "c"), 32 | ("class:testclass", "d"), 33 | ] 34 | assert first_input_line() == [ 35 | ("class:testclass", "e"), 36 | ("class:testclass", "f"), 37 | ] 38 | 39 | # Edge case 1: starting with a newline. 40 | tokens = [("class:testclass", "\nab")] 41 | has_before_tokens, before, first_input_line = _split_multiline_prompt( 42 | lambda: tokens 43 | ) 44 | assert has_before_tokens() is True 45 | assert before() == [] 46 | assert first_input_line() == [("class:testclass", "a"), ("class:testclass", "b")] 47 | 48 | # Edge case 2: starting with two newlines. 49 | tokens = [("class:testclass", "\n\nab")] 50 | has_before_tokens, before, first_input_line = _split_multiline_prompt( 51 | lambda: tokens 52 | ) 53 | assert has_before_tokens() is True 54 | assert before() == [("class:testclass", "\n")] 55 | assert first_input_line() == [("class:testclass", "a"), ("class:testclass", "b")] 56 | 57 | 58 | def test_print_container(tmpdir): 59 | # Call `print_container`, render to a dummy file. 60 | f = tmpdir.join("output") 61 | with open(f, "w") as fd: 62 | print_container(Frame(TextArea(text="Hello world!\n"), title="Title"), file=fd) 63 | 64 | # Verify rendered output. 65 | with open(f) as fd: 66 | text = fd.read() 67 | assert "Hello world" in text 68 | assert "Title" in text 69 | -------------------------------------------------------------------------------- /tests/test_style_transformation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from prompt_toolkit.styles import AdjustBrightnessStyleTransformation, Attrs 6 | 7 | 8 | @pytest.fixture 9 | def default_attrs(): 10 | return Attrs( 11 | color="", 12 | bgcolor="", 13 | bold=False, 14 | underline=False, 15 | strike=False, 16 | italic=False, 17 | blink=False, 18 | reverse=False, 19 | hidden=False, 20 | ) 21 | 22 | 23 | def test_adjust_brightness_style_transformation(default_attrs): 24 | tr = AdjustBrightnessStyleTransformation(0.5, 1.0) 25 | 26 | attrs = tr.transform_attrs(default_attrs._replace(color="ff0000")) 27 | assert attrs.color == "ff7f7f" 28 | 29 | attrs = tr.transform_attrs(default_attrs._replace(color="00ffaa")) 30 | assert attrs.color == "7fffd4" 31 | 32 | # When a background color is given, nothing should change. 33 | attrs = tr.transform_attrs(default_attrs._replace(color="00ffaa", bgcolor="white")) 34 | assert attrs.color == "00ffaa" 35 | 36 | # Test ansi colors. 37 | attrs = tr.transform_attrs(default_attrs._replace(color="ansiblue")) 38 | assert attrs.color == "6666ff" 39 | 40 | # Test 'ansidefault'. This shouldn't change. 41 | attrs = tr.transform_attrs(default_attrs._replace(color="ansidefault")) 42 | assert attrs.color == "ansidefault" 43 | 44 | # When 0 and 1 are given, don't do any style transformation. 45 | tr2 = AdjustBrightnessStyleTransformation(0, 1) 46 | 47 | attrs = tr2.transform_attrs(default_attrs._replace(color="ansiblue")) 48 | assert attrs.color == "ansiblue" 49 | 50 | attrs = tr2.transform_attrs(default_attrs._replace(color="00ffaa")) 51 | assert attrs.color == "00ffaa" 52 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import itertools 4 | 5 | import pytest 6 | 7 | from prompt_toolkit.utils import take_using_weights 8 | 9 | 10 | def test_using_weights(): 11 | def take(generator, count): 12 | return list(itertools.islice(generator, 0, count)) 13 | 14 | # Check distribution. 15 | data = take(take_using_weights(["A", "B", "C"], [5, 10, 20]), 35) 16 | assert data.count("A") == 5 17 | assert data.count("B") == 10 18 | assert data.count("C") == 20 19 | 20 | assert data == [ 21 | "A", 22 | "B", 23 | "C", 24 | "C", 25 | "B", 26 | "C", 27 | "C", 28 | "A", 29 | "B", 30 | "C", 31 | "C", 32 | "B", 33 | "C", 34 | "C", 35 | "A", 36 | "B", 37 | "C", 38 | "C", 39 | "B", 40 | "C", 41 | "C", 42 | "A", 43 | "B", 44 | "C", 45 | "C", 46 | "B", 47 | "C", 48 | "C", 49 | "A", 50 | "B", 51 | "C", 52 | "C", 53 | "B", 54 | "C", 55 | "C", 56 | ] 57 | 58 | # Another order. 59 | data = take(take_using_weights(["A", "B", "C"], [20, 10, 5]), 35) 60 | assert data.count("A") == 20 61 | assert data.count("B") == 10 62 | assert data.count("C") == 5 63 | 64 | # Bigger numbers. 65 | data = take(take_using_weights(["A", "B", "C"], [20, 10, 5]), 70) 66 | assert data.count("A") == 40 67 | assert data.count("B") == 20 68 | assert data.count("C") == 10 69 | 70 | # Negative numbers. 71 | data = take(take_using_weights(["A", "B", "C"], [-20, 10, 0]), 70) 72 | assert data.count("A") == 0 73 | assert data.count("B") == 70 74 | assert data.count("C") == 0 75 | 76 | # All zero-weight items. 77 | with pytest.raises(ValueError): 78 | take(take_using_weights(["A", "B", "C"], [0, 0, 0]), 70) 79 | -------------------------------------------------------------------------------- /tests/test_vt100_output.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from prompt_toolkit.output.vt100 import _get_closest_ansi_color 4 | 5 | 6 | def test_get_closest_ansi_color(): 7 | # White 8 | assert _get_closest_ansi_color(255, 255, 255) == "ansiwhite" 9 | assert _get_closest_ansi_color(250, 250, 250) == "ansiwhite" 10 | 11 | # Black 12 | assert _get_closest_ansi_color(0, 0, 0) == "ansiblack" 13 | assert _get_closest_ansi_color(5, 5, 5) == "ansiblack" 14 | 15 | # Green 16 | assert _get_closest_ansi_color(0, 255, 0) == "ansibrightgreen" 17 | assert _get_closest_ansi_color(10, 255, 0) == "ansibrightgreen" 18 | assert _get_closest_ansi_color(0, 255, 10) == "ansibrightgreen" 19 | 20 | assert _get_closest_ansi_color(220, 220, 100) == "ansiyellow" 21 | -------------------------------------------------------------------------------- /tests/test_widgets.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from prompt_toolkit.formatted_text import fragment_list_to_text 4 | from prompt_toolkit.layout import to_window 5 | from prompt_toolkit.widgets import Button 6 | 7 | 8 | def _to_text(button: Button) -> str: 9 | control = to_window(button).content 10 | return fragment_list_to_text(control.text()) 11 | 12 | 13 | def test_default_button(): 14 | button = Button("Exit") 15 | assert _to_text(button) == "< Exit >" 16 | 17 | 18 | def test_custom_button(): 19 | button = Button("Exit", left_symbol="[", right_symbol="]") 20 | assert _to_text(button) == "[ Exit ]" 21 | -------------------------------------------------------------------------------- /tests/test_yank_nth_arg.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from prompt_toolkit.buffer import Buffer 6 | from prompt_toolkit.history import InMemoryHistory 7 | 8 | 9 | @pytest.fixture 10 | def _history(): 11 | "Prefilled history." 12 | history = InMemoryHistory() 13 | history.append_string("alpha beta gamma delta") 14 | history.append_string("one two three four") 15 | return history 16 | 17 | 18 | # Test yank_last_arg. 19 | 20 | 21 | def test_empty_history(): 22 | buf = Buffer() 23 | buf.yank_last_arg() 24 | assert buf.document.current_line == "" 25 | 26 | 27 | def test_simple_search(_history): 28 | buff = Buffer(history=_history) 29 | buff.yank_last_arg() 30 | assert buff.document.current_line == "four" 31 | 32 | 33 | def test_simple_search_with_quotes(_history): 34 | _history.append_string("""one two "three 'x' four"\n""") 35 | buff = Buffer(history=_history) 36 | buff.yank_last_arg() 37 | assert buff.document.current_line == '''"three 'x' four"''' 38 | 39 | 40 | def test_simple_search_with_arg(_history): 41 | buff = Buffer(history=_history) 42 | buff.yank_last_arg(n=2) 43 | assert buff.document.current_line == "three" 44 | 45 | 46 | def test_simple_search_with_arg_out_of_bounds(_history): 47 | buff = Buffer(history=_history) 48 | buff.yank_last_arg(n=8) 49 | assert buff.document.current_line == "" 50 | 51 | 52 | def test_repeated_search(_history): 53 | buff = Buffer(history=_history) 54 | buff.yank_last_arg() 55 | buff.yank_last_arg() 56 | assert buff.document.current_line == "delta" 57 | 58 | 59 | def test_repeated_search_with_wraparound(_history): 60 | buff = Buffer(history=_history) 61 | buff.yank_last_arg() 62 | buff.yank_last_arg() 63 | buff.yank_last_arg() 64 | assert buff.document.current_line == "four" 65 | 66 | 67 | # Test yank_last_arg. 68 | 69 | 70 | def test_yank_nth_arg(_history): 71 | buff = Buffer(history=_history) 72 | buff.yank_nth_arg() 73 | assert buff.document.current_line == "two" 74 | 75 | 76 | def test_repeated_yank_nth_arg(_history): 77 | buff = Buffer(history=_history) 78 | buff.yank_nth_arg() 79 | buff.yank_nth_arg() 80 | assert buff.document.current_line == "beta" 81 | 82 | 83 | def test_yank_nth_arg_with_arg(_history): 84 | buff = Buffer(history=_history) 85 | buff.yank_nth_arg(n=2) 86 | assert buff.document.current_line == "three" 87 | -------------------------------------------------------------------------------- /tools/debug_input_cross_platform.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Read input and print keys. 4 | For testing terminal input. 5 | 6 | Works on both Windows and Posix. 7 | """ 8 | 9 | import asyncio 10 | 11 | from prompt_toolkit.input import create_input 12 | from prompt_toolkit.keys import Keys 13 | 14 | 15 | async def main() -> None: 16 | done = asyncio.Event() 17 | input = create_input() 18 | 19 | def keys_ready(): 20 | for key_press in input.read_keys(): 21 | print(key_press) 22 | 23 | if key_press.key == Keys.ControlC: 24 | done.set() 25 | 26 | with input.raw_mode(): 27 | with input.attach(keys_ready): 28 | await done.wait() 29 | 30 | 31 | if __name__ == "__main__": 32 | asyncio.run(main()) 33 | -------------------------------------------------------------------------------- /tools/debug_vt100_input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Parse vt100 input and print keys. 4 | For testing terminal input. 5 | 6 | (This does not use the `Input` implementation, but only the `Vt100Parser`.) 7 | """ 8 | 9 | import sys 10 | 11 | from prompt_toolkit.input.vt100 import raw_mode 12 | from prompt_toolkit.input.vt100_parser import Vt100Parser 13 | from prompt_toolkit.keys import Keys 14 | 15 | 16 | def callback(key_press): 17 | print(key_press) 18 | 19 | if key_press.key == Keys.ControlC: 20 | sys.exit(0) 21 | 22 | 23 | def main(): 24 | stream = Vt100Parser(callback) 25 | 26 | with raw_mode(sys.stdin.fileno()): 27 | while True: 28 | c = sys.stdin.read(1) 29 | stream.feed(c) 30 | 31 | 32 | if __name__ == "__main__": 33 | main() 34 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # Tox (http://tox.testrun.org/) is a tool for running tests 2 | # in multiple virtualenvs. This configuration file will run the 3 | # test suite on all supported python versions. To use it, "pip install tox" 4 | # and then run "tox" from this directory. 5 | 6 | [tox] 7 | envlist = py{37, 38, 39, 310, 311, 312, py3} 8 | 9 | [testenv] 10 | commands = pytest [] 11 | 12 | deps= 13 | pytest 14 | --------------------------------------------------------------------------------
      ", 34 | "", 35 | "