From 4f1a3b5f9ad05aa7b08715d48909a2b06ee2fcb1 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 18:35:31 +0200 Subject: Adding upstream version 3.0.43. Signed-off-by: Daniel Baumann --- examples/dialogs/button_dialog.py | 19 + examples/dialogs/checkbox_dialog.py | 36 ++ examples/dialogs/input_dialog.py | 17 + examples/dialogs/messagebox.py | 16 + examples/dialogs/password_dialog.py | 19 + examples/dialogs/progress_dialog.py | 47 +++ examples/dialogs/radio_dialog.py | 39 +++ examples/dialogs/styled_messagebox.py | 37 ++ examples/dialogs/yes_no_dialog.py | 17 + examples/full-screen/ansi-art-and-textarea.py | 82 +++++ examples/full-screen/buttons.py | 91 +++++ examples/full-screen/calculator.py | 95 +++++ examples/full-screen/dummy-app.py | 8 + examples/full-screen/full-screen-demo.py | 225 ++++++++++++ examples/full-screen/hello-world.py | 43 +++ examples/full-screen/no-layout.py | 7 + examples/full-screen/pager.py | 111 ++++++ .../full-screen/scrollable-panes/simple-example.py | 45 +++ .../scrollable-panes/with-completion-menu.py | 120 +++++++ examples/full-screen/simple-demos/alignment.py | 60 ++++ .../full-screen/simple-demos/autocompletion.py | 100 ++++++ examples/full-screen/simple-demos/colorcolumn.py | 63 ++++ .../simple-demos/cursorcolumn-cursorline.py | 59 ++++ .../full-screen/simple-demos/float-transparency.py | 87 +++++ examples/full-screen/simple-demos/floats.py | 116 +++++++ examples/full-screen/simple-demos/focus.py | 98 ++++++ .../full-screen/simple-demos/horizontal-align.py | 208 +++++++++++ .../full-screen/simple-demos/horizontal-split.py | 44 +++ examples/full-screen/simple-demos/line-prefixes.py | 110 ++++++ examples/full-screen/simple-demos/margins.py | 71 ++++ .../full-screen/simple-demos/vertical-align.py | 167 +++++++++ .../full-screen/simple-demos/vertical-split.py | 44 +++ examples/full-screen/split-screen.py | 156 +++++++++ examples/full-screen/text-editor.py | 383 +++++++++++++++++++++ examples/gevent-get-input.py | 24 ++ examples/print-text/ansi-colors.py | 100 ++++++ examples/print-text/ansi.py | 50 +++ examples/print-text/html.py | 53 +++ examples/print-text/named-colors.py | 29 ++ examples/print-text/print-formatted-text.py | 46 +++ examples/print-text/print-frame.py | 14 + .../print-text/prompt-toolkit-logo-ansi-art.py | 40 +++ examples/print-text/pygments-tokens.py | 44 +++ examples/print-text/true-color-demo.py | 35 ++ examples/progress-bar/a-lot-of-parallel-tasks.py | 65 ++++ examples/progress-bar/colored-title-and-label.py | 22 ++ examples/progress-bar/custom-key-bindings.py | 51 +++ examples/progress-bar/many-parallel-tasks.py | 46 +++ examples/progress-bar/nested-progress-bars.py | 22 ++ examples/progress-bar/scrolling-task-name.py | 23 ++ examples/progress-bar/simple-progress-bar.py | 18 + examples/progress-bar/styled-1.py | 36 ++ examples/progress-bar/styled-2.py | 49 +++ examples/progress-bar/styled-apt-get-install.py | 38 ++ examples/progress-bar/styled-rainbow.py | 35 ++ examples/progress-bar/styled-tqdm-1.py | 40 +++ examples/progress-bar/styled-tqdm-2.py | 38 ++ examples/progress-bar/two-tasks.py | 39 +++ examples/progress-bar/unknown-length.py | 26 ++ examples/prompts/accept-default.py | 16 + examples/prompts/asyncio-prompt.py | 63 ++++ .../autocomplete-with-control-space.py | 75 ++++ .../autocompletion-like-readline.py | 58 ++++ examples/prompts/auto-completion/autocompletion.py | 60 ++++ .../colored-completions-with-formatted-text.py | 137 ++++++++ .../prompts/auto-completion/colored-completions.py | 78 +++++ .../auto-completion/combine-multiple-completers.py | 76 ++++ .../auto-completion/fuzzy-custom-completer.py | 56 +++ .../auto-completion/fuzzy-word-completer.py | 59 ++++ .../multi-column-autocompletion-with-meta.py | 50 +++ .../auto-completion/multi-column-autocompletion.py | 57 +++ .../auto-completion/nested-autocompletion.py | 22 ++ .../prompts/auto-completion/slow-completions.py | 103 ++++++ examples/prompts/auto-suggestion.py | 48 +++ examples/prompts/autocorrection.py | 44 +++ examples/prompts/bottom-toolbar.py | 80 +++++ examples/prompts/clock-input.py | 25 ++ examples/prompts/colored-prompt.py | 81 +++++ examples/prompts/confirmation-prompt.py | 9 + examples/prompts/cursor-shapes.py | 19 + examples/prompts/custom-key-binding.py | 77 +++++ examples/prompts/custom-lexer.py | 29 ++ .../prompts/custom-vi-operator-and-text-object.py | 70 ++++ examples/prompts/enforce-tty-input-output.py | 13 + examples/prompts/fancy-zsh-prompt.py | 79 +++++ examples/prompts/finalterm-shell-integration.py | 43 +++ examples/prompts/get-input-vi-mode.py | 7 + examples/prompts/get-input-with-default.py | 12 + examples/prompts/get-input.py | 9 + examples/prompts/get-multiline-input.py | 29 ++ .../get-password-with-toggle-display-shortcut.py | 28 ++ examples/prompts/get-password.py | 6 + examples/prompts/history/persistent-history.py | 25 ++ examples/prompts/history/slow-history.py | 48 +++ examples/prompts/html-input.py | 18 + examples/prompts/input-validation.py | 35 ++ examples/prompts/inputhook.py | 83 +++++ examples/prompts/mouse-support.py | 10 + examples/prompts/multiline-prompt.py | 11 + examples/prompts/no-wrapping.py | 6 + examples/prompts/operate-and-get-next.py | 18 + examples/prompts/patch-stdout.py | 41 +++ examples/prompts/placeholder-text.py | 13 + examples/prompts/regular-language.py | 108 ++++++ examples/prompts/rprompt.py | 53 +++ examples/prompts/swap-light-and-dark-colors.py | 78 +++++ examples/prompts/switch-between-vi-emacs.py | 36 ++ examples/prompts/system-clipboard-integration.py | 17 + examples/prompts/system-prompt.py | 20 ++ examples/prompts/terminal-title.py | 10 + .../prompts/up-arrow-partial-string-matching.py | 41 +++ examples/ssh/asyncssh-server.py | 120 +++++++ examples/telnet/chat-app.py | 103 ++++++ examples/telnet/dialog.py | 34 ++ examples/telnet/hello-world.py | 39 +++ examples/telnet/toolbar.py | 44 +++ examples/tutorial/README.md | 1 + examples/tutorial/sqlite-cli.py | 184 ++++++++++ 118 files changed, 6637 insertions(+) create mode 100755 examples/dialogs/button_dialog.py create mode 100755 examples/dialogs/checkbox_dialog.py create mode 100755 examples/dialogs/input_dialog.py create mode 100755 examples/dialogs/messagebox.py create mode 100755 examples/dialogs/password_dialog.py create mode 100755 examples/dialogs/progress_dialog.py create mode 100755 examples/dialogs/radio_dialog.py create mode 100755 examples/dialogs/styled_messagebox.py create mode 100755 examples/dialogs/yes_no_dialog.py create mode 100755 examples/full-screen/ansi-art-and-textarea.py create mode 100755 examples/full-screen/buttons.py create mode 100755 examples/full-screen/calculator.py create mode 100755 examples/full-screen/dummy-app.py create mode 100755 examples/full-screen/full-screen-demo.py create mode 100755 examples/full-screen/hello-world.py create mode 100644 examples/full-screen/no-layout.py create mode 100755 examples/full-screen/pager.py create mode 100644 examples/full-screen/scrollable-panes/simple-example.py create mode 100644 examples/full-screen/scrollable-panes/with-completion-menu.py create mode 100755 examples/full-screen/simple-demos/alignment.py create mode 100755 examples/full-screen/simple-demos/autocompletion.py create mode 100755 examples/full-screen/simple-demos/colorcolumn.py create mode 100755 examples/full-screen/simple-demos/cursorcolumn-cursorline.py create mode 100755 examples/full-screen/simple-demos/float-transparency.py create mode 100755 examples/full-screen/simple-demos/floats.py create mode 100755 examples/full-screen/simple-demos/focus.py create mode 100755 examples/full-screen/simple-demos/horizontal-align.py create mode 100755 examples/full-screen/simple-demos/horizontal-split.py create mode 100755 examples/full-screen/simple-demos/line-prefixes.py create mode 100755 examples/full-screen/simple-demos/margins.py create mode 100755 examples/full-screen/simple-demos/vertical-align.py create mode 100755 examples/full-screen/simple-demos/vertical-split.py create mode 100755 examples/full-screen/split-screen.py create mode 100755 examples/full-screen/text-editor.py create mode 100755 examples/gevent-get-input.py create mode 100755 examples/print-text/ansi-colors.py create mode 100755 examples/print-text/ansi.py create mode 100755 examples/print-text/html.py create mode 100755 examples/print-text/named-colors.py create mode 100755 examples/print-text/print-formatted-text.py create mode 100755 examples/print-text/print-frame.py create mode 100755 examples/print-text/prompt-toolkit-logo-ansi-art.py create mode 100755 examples/print-text/pygments-tokens.py create mode 100755 examples/print-text/true-color-demo.py create mode 100755 examples/progress-bar/a-lot-of-parallel-tasks.py create mode 100755 examples/progress-bar/colored-title-and-label.py create mode 100755 examples/progress-bar/custom-key-bindings.py create mode 100755 examples/progress-bar/many-parallel-tasks.py create mode 100755 examples/progress-bar/nested-progress-bars.py create mode 100755 examples/progress-bar/scrolling-task-name.py create mode 100755 examples/progress-bar/simple-progress-bar.py create mode 100755 examples/progress-bar/styled-1.py create mode 100755 examples/progress-bar/styled-2.py create mode 100755 examples/progress-bar/styled-apt-get-install.py create mode 100755 examples/progress-bar/styled-rainbow.py create mode 100755 examples/progress-bar/styled-tqdm-1.py create mode 100755 examples/progress-bar/styled-tqdm-2.py create mode 100755 examples/progress-bar/two-tasks.py create mode 100755 examples/progress-bar/unknown-length.py create mode 100644 examples/prompts/accept-default.py create mode 100755 examples/prompts/asyncio-prompt.py create mode 100755 examples/prompts/auto-completion/autocomplete-with-control-space.py create mode 100755 examples/prompts/auto-completion/autocompletion-like-readline.py create mode 100755 examples/prompts/auto-completion/autocompletion.py create mode 100755 examples/prompts/auto-completion/colored-completions-with-formatted-text.py create mode 100755 examples/prompts/auto-completion/colored-completions.py create mode 100755 examples/prompts/auto-completion/combine-multiple-completers.py create mode 100755 examples/prompts/auto-completion/fuzzy-custom-completer.py create mode 100755 examples/prompts/auto-completion/fuzzy-word-completer.py create mode 100755 examples/prompts/auto-completion/multi-column-autocompletion-with-meta.py create mode 100755 examples/prompts/auto-completion/multi-column-autocompletion.py create mode 100755 examples/prompts/auto-completion/nested-autocompletion.py create mode 100755 examples/prompts/auto-completion/slow-completions.py create mode 100755 examples/prompts/auto-suggestion.py create mode 100755 examples/prompts/autocorrection.py create mode 100755 examples/prompts/bottom-toolbar.py create mode 100755 examples/prompts/clock-input.py create mode 100755 examples/prompts/colored-prompt.py create mode 100755 examples/prompts/confirmation-prompt.py create mode 100755 examples/prompts/cursor-shapes.py create mode 100755 examples/prompts/custom-key-binding.py create mode 100755 examples/prompts/custom-lexer.py create mode 100755 examples/prompts/custom-vi-operator-and-text-object.py create mode 100755 examples/prompts/enforce-tty-input-output.py create mode 100755 examples/prompts/fancy-zsh-prompt.py create mode 100755 examples/prompts/finalterm-shell-integration.py create mode 100755 examples/prompts/get-input-vi-mode.py create mode 100755 examples/prompts/get-input-with-default.py create mode 100755 examples/prompts/get-input.py create mode 100755 examples/prompts/get-multiline-input.py create mode 100755 examples/prompts/get-password-with-toggle-display-shortcut.py create mode 100755 examples/prompts/get-password.py create mode 100755 examples/prompts/history/persistent-history.py create mode 100755 examples/prompts/history/slow-history.py create mode 100755 examples/prompts/html-input.py create mode 100755 examples/prompts/input-validation.py create mode 100755 examples/prompts/inputhook.py create mode 100755 examples/prompts/mouse-support.py create mode 100755 examples/prompts/multiline-prompt.py create mode 100755 examples/prompts/no-wrapping.py create mode 100755 examples/prompts/operate-and-get-next.py create mode 100755 examples/prompts/patch-stdout.py create mode 100755 examples/prompts/placeholder-text.py create mode 100755 examples/prompts/regular-language.py create mode 100755 examples/prompts/rprompt.py create mode 100755 examples/prompts/swap-light-and-dark-colors.py create mode 100755 examples/prompts/switch-between-vi-emacs.py create mode 100755 examples/prompts/system-clipboard-integration.py create mode 100755 examples/prompts/system-prompt.py create mode 100755 examples/prompts/terminal-title.py create mode 100755 examples/prompts/up-arrow-partial-string-matching.py create mode 100755 examples/ssh/asyncssh-server.py create mode 100755 examples/telnet/chat-app.py create mode 100755 examples/telnet/dialog.py create mode 100755 examples/telnet/hello-world.py create mode 100755 examples/telnet/toolbar.py create mode 100755 examples/tutorial/README.md create mode 100755 examples/tutorial/sqlite-cli.py (limited to 'examples') diff --git a/examples/dialogs/button_dialog.py b/examples/dialogs/button_dialog.py new file mode 100755 index 0000000..7a99b9a --- /dev/null +++ b/examples/dialogs/button_dialog.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +""" +Example of button dialog window. +""" +from prompt_toolkit.shortcuts import button_dialog + + +def main(): + result = button_dialog( + title="Button dialog example", + text="Are you sure?", + buttons=[("Yes", True), ("No", False), ("Maybe...", None)], + ).run() + + print(f"Result = {result}") + + +if __name__ == "__main__": + main() diff --git a/examples/dialogs/checkbox_dialog.py b/examples/dialogs/checkbox_dialog.py new file mode 100755 index 0000000..90be263 --- /dev/null +++ b/examples/dialogs/checkbox_dialog.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +Example of a checkbox-list-based dialog. +""" +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import checkboxlist_dialog, message_dialog +from prompt_toolkit.styles import Style + +results = checkboxlist_dialog( + title="CheckboxList dialog", + text="What would you like in your breakfast ?", + values=[ + ("eggs", "Eggs"), + ("bacon", HTML("Bacon")), + ("croissants", "20 Croissants"), + ("daily", "The breakfast of the day"), + ], + style=Style.from_dict( + { + "dialog": "bg:#cdbbb3", + "button": "bg:#bf99a4", + "checkbox": "#e8612c", + "dialog.body": "bg:#a9cfd0", + "dialog shadow": "bg:#c98982", + "frame.label": "#fcaca3", + "dialog.body label": "#fd8bb6", + } + ), +).run() +if results: + message_dialog( + title="Room service", + text="You selected: %s\nGreat choice sir !" % ",".join(results), + ).run() +else: + message_dialog("*starves*").run() diff --git a/examples/dialogs/input_dialog.py b/examples/dialogs/input_dialog.py new file mode 100755 index 0000000..6235265 --- /dev/null +++ b/examples/dialogs/input_dialog.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +""" +Example of an input box dialog. +""" +from prompt_toolkit.shortcuts import input_dialog + + +def main(): + result = input_dialog( + title="Input dialog example", text="Please type your name:" + ).run() + + print(f"Result = {result}") + + +if __name__ == "__main__": + main() diff --git a/examples/dialogs/messagebox.py b/examples/dialogs/messagebox.py new file mode 100755 index 0000000..4642b84 --- /dev/null +++ b/examples/dialogs/messagebox.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +""" +Example of a message box window. +""" +from prompt_toolkit.shortcuts import message_dialog + + +def main(): + message_dialog( + title="Example dialog window", + text="Do you want to continue?\nPress ENTER to quit.", + ).run() + + +if __name__ == "__main__": + main() diff --git a/examples/dialogs/password_dialog.py b/examples/dialogs/password_dialog.py new file mode 100755 index 0000000..39d7b9c --- /dev/null +++ b/examples/dialogs/password_dialog.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +""" +Example of an password input dialog. +""" +from prompt_toolkit.shortcuts import input_dialog + + +def main(): + result = input_dialog( + title="Password dialog example", + text="Please type your password:", + password=True, + ).run() + + print(f"Result = {result}") + + +if __name__ == "__main__": + main() diff --git a/examples/dialogs/progress_dialog.py b/examples/dialogs/progress_dialog.py new file mode 100755 index 0000000..1fd3ffb --- /dev/null +++ b/examples/dialogs/progress_dialog.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python +""" +Example of a progress bar dialog. +""" +import os +import time + +from prompt_toolkit.shortcuts import progress_dialog + + +def worker(set_percentage, log_text): + """ + This worker function is called by `progress_dialog`. It will run in a + background thread. + + The `set_percentage` function can be used to update the progress bar, while + the `log_text` function can be used to log text in the logging window. + """ + percentage = 0 + for dirpath, dirnames, filenames in os.walk("../.."): + for f in filenames: + log_text(f"{dirpath} / {f}\n") + set_percentage(percentage + 1) + percentage += 2 + time.sleep(0.1) + + if percentage == 100: + break + if percentage == 100: + break + + # Show 100% for a second, before quitting. + set_percentage(100) + time.sleep(1) + + +def main(): + progress_dialog( + title="Progress dialog example", + text="As an examples, we walk through the filesystem and print " + "all directories", + run_callback=worker, + ).run() + + +if __name__ == "__main__": + main() diff --git a/examples/dialogs/radio_dialog.py b/examples/dialogs/radio_dialog.py new file mode 100755 index 0000000..94d80e2 --- /dev/null +++ b/examples/dialogs/radio_dialog.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +""" +Example of a radio list box dialog. +""" +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import radiolist_dialog + + +def main(): + result = radiolist_dialog( + values=[ + ("red", "Red"), + ("green", "Green"), + ("blue", "Blue"), + ("orange", "Orange"), + ], + title="Radiolist dialog example", + text="Please select a color:", + ).run() + + print(f"Result = {result}") + + # With HTML. + result = radiolist_dialog( + values=[ + ("red", HTML('')), + ("green", HTML('')), + ("blue", HTML('')), + ("orange", HTML('')), + ], + title=HTML("Radiolist dialog example with colors"), + text="Please select a color:", + ).run() + + print(f"Result = {result}") + + +if __name__ == "__main__": + main() diff --git a/examples/dialogs/styled_messagebox.py b/examples/dialogs/styled_messagebox.py new file mode 100755 index 0000000..3f6fc53 --- /dev/null +++ b/examples/dialogs/styled_messagebox.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +""" +Example of a style dialog window. +All dialog shortcuts take a `style` argument in order to apply a custom +styling. + +This also demonstrates that the `title` argument can be any kind of formatted +text. +""" +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import message_dialog +from prompt_toolkit.styles import Style + +# Custom color scheme. +example_style = Style.from_dict( + { + "dialog": "bg:#88ff88", + "dialog frame-label": "bg:#ffffff #000000", + "dialog.body": "bg:#000000 #00ff00", + "dialog shadow": "bg:#00aa00", + } +) + + +def main(): + message_dialog( + title=HTML( + ' ' + ' window' + ), + text="Do you want to continue?\nPress ENTER to quit.", + style=example_style, + ).run() + + +if __name__ == "__main__": + main() diff --git a/examples/dialogs/yes_no_dialog.py b/examples/dialogs/yes_no_dialog.py new file mode 100755 index 0000000..4b08dd6 --- /dev/null +++ b/examples/dialogs/yes_no_dialog.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +""" +Example of confirmation (yes/no) dialog window. +""" +from prompt_toolkit.shortcuts import yes_no_dialog + + +def main(): + result = yes_no_dialog( + title="Yes/No dialog example", text="Do you want to confirm?" + ).run() + + print(f"Result = {result}") + + +if __name__ == "__main__": + main() diff --git a/examples/full-screen/ansi-art-and-textarea.py b/examples/full-screen/ansi-art-and-textarea.py new file mode 100755 index 0000000..c0a59fd --- /dev/null +++ b/examples/full-screen/ansi-art-and-textarea.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python + +from prompt_toolkit.application import Application +from prompt_toolkit.formatted_text import ANSI, HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout import HSplit, Layout, VSplit, WindowAlign +from prompt_toolkit.widgets import Dialog, Label, TextArea + + +def main(): + # Key bindings. + kb = KeyBindings() + + @kb.add("c-c") + def _(event): + "Quit when control-c is pressed." + event.app.exit() + + text_area = TextArea(text="You can type here...") + dialog_body = HSplit( + [ + Label( + HTML("Press control-c to quit."), + align=WindowAlign.CENTER, + ), + VSplit( + [ + Label(PROMPT_TOOLKIT_LOGO, align=WindowAlign.CENTER), + text_area, + ], + ), + ] + ) + + application = Application( + layout=Layout( + container=Dialog( + title="ANSI Art demo - Art on the left, text area on the right", + body=dialog_body, + with_background=True, + ), + focused_element=text_area, + ), + full_screen=True, + mouse_support=True, + key_bindings=kb, + ) + application.run() + + +PROMPT_TOOLKIT_LOGO = ANSI( + """ +\x1b[48;2;0;0;0m \x1b[m +\x1b[48;2;0;0;0m \x1b[48;2;0;249;0m\x1b[38;2;0;0;0m▀\x1b[48;2;0;209;0m▀\x1b[48;2;0;207;0m\x1b[38;2;6;34;6m▀\x1b[48;2;0;66;0m\x1b[38;2;30;171;30m▀\x1b[48;2;0;169;0m\x1b[38;2;51;35;51m▀\x1b[48;2;0;248;0m\x1b[38;2;49;194;49m▀\x1b[48;2;0;111;0m\x1b[38;2;25;57;25m▀\x1b[48;2;140;195;140m\x1b[38;2;3;17;3m▀\x1b[48;2;30;171;30m\x1b[38;2;0;0;0m▀\x1b[48;2;0;0;0m \x1b[m +\x1b[48;2;0;0;0m \x1b[48;2;77;127;78m\x1b[38;2;118;227;108m▀\x1b[48;2;216;1;13m\x1b[38;2;49;221;57m▀\x1b[48;2;26;142;76m\x1b[38;2;108;146;165m▀\x1b[48;2;26;142;90m\x1b[38;2;209;197;114m▀▀\x1b[38;2;209;146;114m▀\x1b[48;2;26;128;90m\x1b[38;2;158;197;114m▀\x1b[48;2;58;210;70m\x1b[38;2;223;152;89m▀\x1b[48;2;232;139;44m\x1b[38;2;97;121;146m▀\x1b[48;2;233;139;45m\x1b[38;2;140;188;183m▀\x1b[48;2;231;139;44m\x1b[38;2;40;168;8m▀\x1b[48;2;228;140;44m\x1b[38;2;37;169;7m▀\x1b[48;2;227;140;44m\x1b[38;2;36;169;7m▀\x1b[48;2;211;142;41m\x1b[38;2;23;171;5m▀\x1b[48;2;86;161;17m\x1b[38;2;2;174;1m▀\x1b[48;2;0;175;0m \x1b[48;2;0;254;0m\x1b[38;2;190;119;190m▀\x1b[48;2;92;39;23m\x1b[38;2;125;50;114m▀\x1b[48;2;43;246;41m\x1b[38;2;49;10;165m▀\x1b[48;2;12;128;90m\x1b[38;2;209;197;114m▀\x1b[48;2;26;128;90m▀▀▀▀\x1b[48;2;26;128;76m▀\x1b[48;2;26;128;90m\x1b[38;2;209;247;114m▀▀\x1b[38;2;209;197;114m▀\x1b[48;2;26;128;76m\x1b[38;2;209;247;114m▀\x1b[48;2;26;128;90m▀▀▀\x1b[48;2;26;128;76m▀\x1b[48;2;26;128;90m▀▀\x1b[48;2;12;128;76m▀\x1b[48;2;12;113;90m\x1b[38;2;209;247;64m▀\x1b[38;2;209;247;114m▀\x1b[48;2;12;128;90m▀\x1b[48;2;12;113;90m▀\x1b[48;2;12;113;76m\x1b[38;2;209;247;64m▀\x1b[48;2;12;128;90m▀\x1b[48;2;12;113;90m▀\x1b[48;2;12;113;76m\x1b[38;2;209;247;114m▀\x1b[48;2;12;113;90m\x1b[38;2;209;247;64m▀\x1b[48;2;26;128;90m\x1b[38;2;151;129;163m▀\x1b[48;2;115;120;103m\x1b[38;2;62;83;227m▀\x1b[48;2;138;14;25m\x1b[38;2;104;106;160m▀\x1b[48;2;0;0;57m\x1b[38;2;0;0;0m▀\x1b[m +\x1b[48;2;249;147;8m\x1b[38;2;172;69;38m▀\x1b[48;2;197;202;10m\x1b[38;2;82;192;58m▀\x1b[48;2;248;124;45m\x1b[38;2;251;131;47m▀\x1b[48;2;248;124;44m▀\x1b[48;2;248;124;45m▀▀\x1b[48;2;248;124;44m▀\x1b[48;2;248;124;45m▀\x1b[48;2;248;125;45m\x1b[38;2;251;130;47m▀\x1b[48;2;248;124;45m\x1b[38;2;252;130;47m▀\x1b[48;2;248;125;45m\x1b[38;2;252;131;47m▀\x1b[38;2;252;130;47m▀\x1b[38;2;252;131;47m▀▀\x1b[48;2;249;125;45m\x1b[38;2;255;130;48m▀\x1b[48;2;233;127;42m\x1b[38;2;190;141;35m▀\x1b[48;2;57;163;10m\x1b[38;2;13;172;3m▀\x1b[48;2;0;176;0m\x1b[38;2;0;175;0m▀\x1b[48;2;7;174;1m\x1b[38;2;35;169;7m▀\x1b[48;2;178;139;32m\x1b[38;2;220;136;41m▀\x1b[48;2;252;124;45m\x1b[38;2;253;131;47m▀\x1b[48;2;248;125;45m\x1b[38;2;251;131;47m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;248;125;44m▀\x1b[48;2;248;135;61m\x1b[38;2;251;132;48m▀\x1b[48;2;250;173;122m\x1b[38;2;251;133;50m▀\x1b[48;2;249;155;93m\x1b[38;2;251;132;49m▀\x1b[48;2;248;132;55m\x1b[38;2;251;132;48m▀\x1b[48;2;250;173;122m\x1b[38;2;251;134;51m▀\x1b[48;2;250;163;106m\x1b[38;2;251;134;50m▀\x1b[48;2;248;128;49m\x1b[38;2;251;132;47m▀\x1b[48;2;250;166;110m\x1b[38;2;251;135;52m▀\x1b[48;2;250;175;125m\x1b[38;2;251;136;54m▀\x1b[48;2;248;132;56m\x1b[38;2;251;132;48m▀\x1b[48;2;248;220;160m\x1b[38;2;105;247;172m▀\x1b[48;2;62;101;236m\x1b[38;2;11;207;160m▀\x1b[m +\x1b[48;2;138;181;197m\x1b[38;2;205;36;219m▀\x1b[48;2;177;211;200m\x1b[38;2;83;231;105m▀\x1b[48;2;242;113;40m\x1b[38;2;245;119;42m▀\x1b[48;2;243;113;41m▀\x1b[48;2;245;114;41m▀▀▀▀▀▀▀▀\x1b[38;2;245;119;43m▀▀▀\x1b[48;2;247;114;41m\x1b[38;2;246;119;43m▀\x1b[48;2;202;125;34m\x1b[38;2;143;141;25m▀\x1b[48;2;84;154;14m\x1b[38;2;97;152;17m▀\x1b[48;2;36;166;6m▀\x1b[48;2;139;140;23m\x1b[38;2;183;133;32m▀\x1b[48;2;248;114;41m\x1b[38;2;248;118;43m▀\x1b[48;2;245;115;41m\x1b[38;2;245;119;43m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[38;2;245;119;42m▀\x1b[48;2;246;117;44m\x1b[38;2;246;132;62m▀\x1b[48;2;246;123;54m\x1b[38;2;249;180;138m▀\x1b[48;2;246;120;49m\x1b[38;2;247;157;102m▀\x1b[48;2;246;116;42m\x1b[38;2;246;127;54m▀\x1b[48;2;246;121;50m\x1b[38;2;248;174;128m▀\x1b[48;2;246;120;48m\x1b[38;2;248;162;110m▀\x1b[48;2;246;116;41m\x1b[38;2;245;122;47m▀\x1b[48;2;246;118;46m\x1b[38;2;248;161;108m▀\x1b[48;2;244;118;47m\x1b[38;2;248;171;123m▀\x1b[48;2;243;115;42m\x1b[38;2;246;127;54m▀\x1b[48;2;179;52;29m\x1b[38;2;86;152;223m▀\x1b[48;2;141;225;95m\x1b[38;2;247;146;130m▀\x1b[m +\x1b[48;2;50;237;108m\x1b[38;2;94;70;153m▀\x1b[48;2;206;221;133m\x1b[38;2;64;240;39m▀\x1b[48;2;233;100;36m\x1b[38;2;240;107;38m▀\x1b[48;2;114;56;22m\x1b[38;2;230;104;37m▀\x1b[48;2;24;20;10m\x1b[38;2;193;90;33m▀\x1b[48;2;21;19;9m\x1b[38;2;186;87;32m▀▀▀▀▀▀▀\x1b[38;2;186;87;33m▀▀▀\x1b[48;2;22;18;10m\x1b[38;2;189;86;33m▀\x1b[48;2;18;36;8m\x1b[38;2;135;107;24m▀\x1b[48;2;3;153;2m\x1b[38;2;5;171;1m▀\x1b[48;2;0;177;0m \x1b[48;2;4;158;2m\x1b[38;2;69;147;12m▀\x1b[48;2;19;45;8m\x1b[38;2;185;89;32m▀\x1b[48;2;22;17;10m\x1b[38;2;186;87;33m▀\x1b[48;2;21;19;9m▀▀▀▀▀▀▀▀\x1b[48;2;21;19;10m▀▀\x1b[48;2;21;19;9m▀▀▀▀\x1b[48;2;21;19;10m▀▀▀\x1b[38;2;186;87;32m▀▀\x1b[48;2;21;19;9m\x1b[38;2;186;87;33m▀\x1b[48;2;21;19;10m\x1b[38;2;186;87;32m▀▀\x1b[48;2;21;19;9m\x1b[38;2;186;87;33m▀\x1b[48;2;22;19;10m\x1b[38;2;191;89;33m▀\x1b[48;2;95;49;20m\x1b[38;2;226;103;37m▀\x1b[48;2;227;99;36m\x1b[38;2;241;109;39m▀\x1b[48;2;80;140;154m\x1b[38;2;17;240;92m▀\x1b[48;2;221;58;175m\x1b[38;2;71;14;245m▀\x1b[m +\x1b[48;2;195;38;42m\x1b[38;2;5;126;86m▀\x1b[48;2;139;230;67m\x1b[38;2;253;201;228m▀\x1b[48;2;208;82;30m\x1b[38;2;213;89;32m▀\x1b[48;2;42;26;12m\x1b[38;2;44;27;12m▀\x1b[48;2;9;14;7m\x1b[38;2;8;13;7m▀\x1b[48;2;11;15;8m\x1b[38;2;10;14;7m▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;11;12;8m\x1b[38;2;10;17;7m▀\x1b[48;2;7;71;5m\x1b[38;2;4;120;3m▀\x1b[48;2;1;164;1m\x1b[38;2;0;178;0m▀\x1b[48;2;4;118;3m\x1b[38;2;0;177;0m▀\x1b[48;2;5;108;3m\x1b[38;2;4;116;3m▀\x1b[48;2;7;75;5m\x1b[38;2;10;23;7m▀\x1b[48;2;10;33;7m\x1b[38;2;10;12;7m▀\x1b[48;2;11;13;8m\x1b[38;2;10;14;7m▀\x1b[48;2;11;14;8m▀\x1b[48;2;11;15;8m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;10;14;7m\x1b[38;2;9;14;7m▀\x1b[48;2;30;21;10m\x1b[38;2;30;22;10m▀\x1b[48;2;195;79;29m\x1b[38;2;200;84;31m▀\x1b[48;2;205;228;23m\x1b[38;2;111;40;217m▀\x1b[48;2;9;217;69m\x1b[38;2;115;137;104m▀\x1b[m +\x1b[48;2;106;72;209m\x1b[38;2;151;183;253m▀\x1b[48;2;120;239;0m\x1b[38;2;25;2;162m▀\x1b[48;2;203;72;26m\x1b[38;2;206;77;28m▀\x1b[48;2;42;24;11m\x1b[38;2;42;25;11m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;13;8m\x1b[38;2;10;28;7m▀\x1b[48;2;9;36;6m\x1b[38;2;7;78;5m▀\x1b[48;2;2;153;1m\x1b[38;2;6;94;4m▀\x1b[48;2;0;178;0m\x1b[38;2;2;156;1m▀\x1b[48;2;0;175;0m\x1b[38;2;1;167;1m▀\x1b[48;2;0;177;0m\x1b[38;2;2;145;2m▀\x1b[48;2;2;147;2m\x1b[38;2;8;54;6m▀\x1b[48;2;9;38;6m\x1b[38;2;11;13;8m▀\x1b[48;2;11;13;8m\x1b[38;2;11;14;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;29;20;10m\x1b[38;2;29;21;10m▀\x1b[48;2;190;69;25m\x1b[38;2;193;74;27m▀\x1b[48;2;136;91;148m\x1b[38;2;42;159;86m▀\x1b[48;2;89;85;149m\x1b[38;2;160;5;219m▀\x1b[m +\x1b[48;2;229;106;143m\x1b[38;2;40;239;187m▀\x1b[48;2;196;134;237m\x1b[38;2;6;11;95m▀\x1b[48;2;197;60;22m\x1b[38;2;201;67;24m▀\x1b[48;2;41;22;10m\x1b[38;2;41;23;11m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[48;2;10;14;7m\x1b[38;2;11;15;8m▀▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;16;7m▀\x1b[48;2;11;15;7m\x1b[38;2;7;79;5m▀\x1b[48;2;7;68;5m\x1b[38;2;1;164;1m▀\x1b[48;2;2;153;1m\x1b[38;2;0;176;0m▀\x1b[48;2;2;154;1m\x1b[38;2;0;175;0m▀\x1b[48;2;5;107;3m\x1b[38;2;1;171;1m▀\x1b[48;2;4;115;3m\x1b[38;2;5;105;3m▀\x1b[48;2;6;84;4m\x1b[38;2;11;18;7m▀\x1b[48;2;10;30;7m\x1b[38;2;11;13;8m▀\x1b[48;2;11;13;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;14;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;29;19;9m\x1b[38;2;29;20;10m▀\x1b[48;2;185;58;22m\x1b[38;2;188;64;24m▀\x1b[48;2;68;241;49m\x1b[38;2;199;22;211m▀\x1b[48;2;133;139;8m\x1b[38;2;239;129;78m▀\x1b[m +\x1b[48;2;74;30;32m\x1b[38;2;163;185;76m▀\x1b[48;2;110;172;9m\x1b[38;2;177;1;123m▀\x1b[48;2;189;43;16m\x1b[38;2;193;52;19m▀\x1b[48;2;39;20;9m\x1b[38;2;40;21;10m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;9;14;7m\x1b[38;2;11;14;8m▀\x1b[48;2;106;54;38m\x1b[38;2;31;24;15m▀\x1b[48;2;164;71;49m\x1b[38;2;24;20;12m▀\x1b[48;2;94;46;31m\x1b[38;2;8;14;7m▀\x1b[48;2;36;24;15m\x1b[38;2;9;14;7m▀\x1b[48;2;11;15;8m\x1b[38;2;11;14;7m▀\x1b[48;2;8;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;10;14;7m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;13;8m▀\x1b[48;2;11;13;8m\x1b[38;2;9;45;6m▀\x1b[48;2;10;19;7m\x1b[38;2;7;75;5m▀\x1b[48;2;6;83;4m\x1b[38;2;2;143;2m▀\x1b[48;2;2;156;1m\x1b[38;2;0;176;0m▀\x1b[48;2;0;177;0m\x1b[38;2;0;175;0m▀\x1b[38;2;3;134;2m▀\x1b[48;2;2;152;1m\x1b[38;2;9;46;6m▀\x1b[48;2;8;60;5m\x1b[38;2;11;13;8m▀\x1b[48;2;11;14;7m\x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;28;18;9m \x1b[48;2;177;43;16m\x1b[38;2;181;51;19m▀\x1b[48;2;93;35;236m\x1b[38;2;224;10;142m▀\x1b[48;2;72;51;52m\x1b[38;2;213;112;158m▀\x1b[m +\x1b[48;2;175;209;155m\x1b[38;2;7;131;221m▀\x1b[48;2;24;0;85m\x1b[38;2;44;86;152m▀\x1b[48;2;181;27;10m\x1b[38;2;185;35;13m▀\x1b[48;2;38;17;8m\x1b[38;2;39;18;9m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m \x1b[48;2;9;14;7m \x1b[48;2;87;43;32m\x1b[38;2;114;54;39m▀\x1b[48;2;188;71;54m\x1b[38;2;211;82;59m▀\x1b[48;2;203;73;55m\x1b[38;2;204;80;57m▀\x1b[48;2;205;73;55m\x1b[38;2;178;71;51m▀\x1b[48;2;204;74;55m\x1b[38;2;119;52;37m▀\x1b[48;2;188;69;52m\x1b[38;2;54;29;19m▀\x1b[48;2;141;55;41m\x1b[38;2;16;17;9m▀\x1b[48;2;75;35;24m\x1b[38;2;8;14;7m▀\x1b[48;2;26;20;12m\x1b[38;2;10;14;7m▀\x1b[48;2;9;14;7m\x1b[38;2;11;14;7m▀\x1b[38;2;11;15;8m▀\x1b[48;2;11;14;7m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m \x1b[48;2;11;13;8m\x1b[38;2;9;45;6m▀\x1b[48;2;10;23;7m\x1b[38;2;4;123;3m▀\x1b[48;2;7;75;5m\x1b[38;2;1;172;1m▀\x1b[48;2;6;84;4m\x1b[38;2;2;154;1m▀\x1b[48;2;4;114;3m\x1b[38;2;5;107;3m▀\x1b[48;2;5;103;4m\x1b[38;2;10;29;7m▀\x1b[48;2;10;23;7m\x1b[38;2;11;13;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;27;16;8m\x1b[38;2;27;17;9m▀\x1b[48;2;170;27;10m\x1b[38;2;174;35;13m▀\x1b[48;2;118;117;199m\x1b[38;2;249;61;74m▀\x1b[48;2;10;219;61m\x1b[38;2;187;245;202m▀\x1b[m +\x1b[48;2;20;155;44m\x1b[38;2;86;54;110m▀\x1b[48;2;195;85;113m\x1b[38;2;214;171;227m▀\x1b[48;2;173;10;4m\x1b[38;2;177;19;7m▀\x1b[48;2;37;14;7m\x1b[38;2;37;16;8m▀\x1b[48;2;9;15;8m\x1b[38;2;9;14;7m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;7m▀\x1b[48;2;11;14;7m\x1b[38;2;15;17;9m▀\x1b[48;2;9;14;7m\x1b[38;2;50;29;20m▀\x1b[48;2;10;15;8m\x1b[38;2;112;47;36m▀\x1b[48;2;33;22;15m\x1b[38;2;170;61;48m▀\x1b[48;2;88;38;29m\x1b[38;2;197;66;53m▀\x1b[48;2;151;53;43m\x1b[38;2;201;67;53m▀\x1b[48;2;189;60;50m▀\x1b[48;2;198;60;51m\x1b[38;2;194;65;52m▀\x1b[38;2;160;56;44m▀\x1b[48;2;196;60;50m\x1b[38;2;99;40;30m▀\x1b[48;2;174;55;47m\x1b[38;2;41;24;16m▀\x1b[48;2;122;43;35m\x1b[38;2;12;15;8m▀\x1b[48;2;59;27;20m\x1b[38;2;8;14;7m▀\x1b[48;2;16;16;9m\x1b[38;2;10;14;7m▀\x1b[48;2;10;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;12;8m▀\x1b[48;2;10;25;7m\x1b[38;2;7;79;5m▀\x1b[48;2;3;141;2m\x1b[38;2;1;174;1m▀\x1b[48;2;0;178;0m\x1b[38;2;1;169;1m▀\x1b[48;2;6;88;4m\x1b[38;2;8;56;6m▀\x1b[48;2;11;12;8m \x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;26;15;8m\x1b[38;2;27;15;8m▀\x1b[48;2;162;12;5m\x1b[38;2;166;20;8m▀\x1b[48;2;143;168;130m\x1b[38;2;18;142;37m▀\x1b[48;2;240;96;105m\x1b[38;2;125;158;211m▀\x1b[m +\x1b[48;2;54;0;0m\x1b[38;2;187;22;0m▀\x1b[48;2;204;0;0m\x1b[38;2;128;208;0m▀\x1b[48;2;162;1;1m\x1b[38;2;168;3;1m▀\x1b[48;2;35;13;7m\x1b[38;2;36;13;7m▀\x1b[48;2;9;15;8m \x1b[48;2;11;15;8m \x1b[38;2;11;14;7m▀\x1b[38;2;9;14;7m▀\x1b[38;2;8;14;7m▀\x1b[48;2;10;14;7m\x1b[38;2;21;18;11m▀\x1b[48;2;7;13;6m\x1b[38;2;65;30;23m▀\x1b[48;2;12;16;9m\x1b[38;2;129;45;38m▀\x1b[48;2;57;29;23m\x1b[38;2;176;53;47m▀\x1b[48;2;148;49;44m\x1b[38;2;191;53;48m▀\x1b[48;2;187;52;48m\x1b[38;2;192;53;48m▀\x1b[48;2;186;51;47m\x1b[38;2;194;54;49m▀\x1b[48;2;182;52;47m\x1b[38;2;178;52;46m▀\x1b[48;2;59;27;21m\x1b[38;2;53;26;19m▀\x1b[48;2;8;14;7m \x1b[48;2;11;15;8m \x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;12;8m\x1b[38;2;11;14;8m▀\x1b[48;2;10;30;7m\x1b[38;2;10;23;7m▀\x1b[48;2;5;110;3m\x1b[38;2;3;138;2m▀\x1b[48;2;2;149;2m\x1b[38;2;0;181;0m▀\x1b[48;2;6;92;4m\x1b[38;2;5;100;4m▀\x1b[48;2;11;13;8m \x1b[48;2;11;14;8m \x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;25;14;7m\x1b[38;2;26;14;7m▀\x1b[48;2;152;2;1m\x1b[38;2;158;5;2m▀\x1b[48;2;6;0;0m\x1b[38;2;44;193;0m▀\x1b[48;2;108;0;0m\x1b[38;2;64;70;0m▀\x1b[m +\x1b[48;2;44;0;0m\x1b[38;2;177;0;0m▀\x1b[48;2;147;0;0m\x1b[38;2;71;0;0m▀\x1b[48;2;148;1;1m\x1b[38;2;155;1;1m▀\x1b[48;2;33;13;7m\x1b[38;2;34;13;7m▀\x1b[48;2;9;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;10;14;7m▀\x1b[48;2;9;14;7m▀\x1b[48;2;13;16;9m\x1b[38;2;11;14;7m▀\x1b[48;2;42;24;17m\x1b[38;2;9;14;7m▀\x1b[48;2;97;38;32m\x1b[38;2;10;15;8m▀\x1b[48;2;149;49;44m\x1b[38;2;30;21;14m▀\x1b[48;2;174;52;48m\x1b[38;2;79;34;28m▀\x1b[48;2;178;52;48m\x1b[38;2;136;45;40m▀\x1b[38;2;172;51;47m▀\x1b[48;2;173;52;48m\x1b[38;2;181;52;48m▀\x1b[48;2;147;47;42m\x1b[38;2;183;52;48m▀\x1b[48;2;94;35;30m\x1b[38;2;177;52;48m▀\x1b[48;2;25;19;12m\x1b[38;2;56;27;20m▀\x1b[48;2;10;14;7m\x1b[38;2;8;14;7m▀\x1b[48;2;11;12;8m\x1b[38;2;11;15;8m▀\x1b[48;2;10;23;7m\x1b[38;2;11;14;8m▀\x1b[48;2;7;76;5m\x1b[38;2;11;13;8m▀\x1b[48;2;2;152;1m\x1b[38;2;9;45;6m▀\x1b[48;2;0;177;0m\x1b[38;2;5;106;3m▀\x1b[48;2;0;178;0m\x1b[38;2;4;123;3m▀\x1b[48;2;1;168;1m\x1b[38;2;5;104;3m▀\x1b[48;2;8;53;6m\x1b[38;2;9;47;6m▀\x1b[48;2;11;12;8m\x1b[38;2;11;13;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;24;14;7m\x1b[38;2;25;14;7m▀\x1b[48;2;140;2;1m\x1b[38;2;146;2;1m▀\x1b[48;2;219;0;0m\x1b[38;2;225;0;0m▀\x1b[48;2;126;0;0m\x1b[38;2;117;0;0m▀\x1b[m +\x1b[48;2;34;0;0m\x1b[38;2;167;0;0m▀\x1b[48;2;89;0;0m\x1b[38;2;14;0;0m▀\x1b[48;2;134;1;1m\x1b[38;2;141;1;1m▀\x1b[48;2;31;13;7m\x1b[38;2;32;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;10;14;7m\x1b[38;2;11;14;7m▀\x1b[48;2;53;29;22m\x1b[38;2;10;14;7m▀\x1b[48;2;127;46;41m\x1b[38;2;20;18;11m▀\x1b[48;2;158;51;47m\x1b[38;2;57;28;22m▀\x1b[48;2;166;52;48m\x1b[38;2;113;42;36m▀\x1b[48;2;167;52;48m\x1b[38;2;156;50;46m▀\x1b[48;2;164;52;48m\x1b[38;2;171;52;48m▀\x1b[48;2;146;48;44m\x1b[38;2;172;52;48m▀\x1b[48;2;102;38;33m▀\x1b[48;2;50;26;19m\x1b[38;2;161;51;46m▀\x1b[48;2;17;17;10m\x1b[38;2;126;44;38m▀\x1b[48;2;8;14;7m\x1b[38;2;71;31;25m▀\x1b[48;2;10;14;7m\x1b[38;2;27;19;13m▀\x1b[48;2;11;13;8m\x1b[38;2;10;14;7m▀\x1b[48;2;9;40;6m\x1b[38;2;10;13;7m▀\x1b[48;2;4;119;3m\x1b[38;2;11;20;7m▀\x1b[48;2;1;168;1m\x1b[38;2;8;63;5m▀\x1b[48;2;0;177;0m\x1b[38;2;3;130;2m▀\x1b[48;2;0;175;0m\x1b[38;2;1;171;1m▀\x1b[48;2;1;174;1m\x1b[38;2;0;176;0m▀\x1b[48;2;1;175;1m\x1b[38;2;1;174;1m▀\x1b[48;2;0;177;0m\x1b[38;2;0;176;0m▀\x1b[48;2;3;134;2m\x1b[38;2;2;158;1m▀\x1b[48;2;10;21;7m\x1b[38;2;9;38;6m▀\x1b[48;2;11;14;8m\x1b[38;2;11;13;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;23;14;7m \x1b[48;2;127;2;1m\x1b[38;2;133;2;1m▀\x1b[48;2;176;0;0m\x1b[38;2;213;0;0m▀\x1b[48;2;109;0;0m\x1b[38;2;100;0;0m▀\x1b[m +\x1b[48;2;24;0;0m\x1b[38;2;157;0;0m▀\x1b[48;2;32;0;0m\x1b[38;2;165;0;0m▀\x1b[48;2;121;1;1m\x1b[38;2;128;1;1m▀\x1b[48;2;28;13;7m\x1b[38;2;30;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m \x1b[48;2;9;15;7m \x1b[48;2;88;41;34m\x1b[38;2;91;41;34m▀\x1b[48;2;145;51;47m\x1b[38;2;163;53;49m▀\x1b[48;2;107;42;36m\x1b[38;2;161;52;48m▀\x1b[48;2;58;29;22m\x1b[38;2;155;51;47m▀\x1b[48;2;21;18;11m\x1b[38;2;128;45;40m▀\x1b[48;2;9;14;7m\x1b[38;2;79;33;27m▀\x1b[38;2;33;21;15m▀\x1b[48;2;11;14;7m\x1b[38;2;12;15;8m▀\x1b[48;2;11;15;8m\x1b[38;2;9;14;7m▀\x1b[38;2;10;14;7m▀ \x1b[48;2;11;12;8m\x1b[38;2;11;14;8m▀\x1b[48;2;8;54;6m\x1b[38;2;10;28;7m▀\x1b[48;2;6;93;4m\x1b[38;2;4;125;3m▀\x1b[48;2;2;152;1m\x1b[38;2;0;175;0m▀\x1b[48;2;0;176;0m▀\x1b[48;2;0;175;0m\x1b[38;2;1;174;1m▀\x1b[48;2;0;177;0m\x1b[38;2;1;175;1m▀\x1b[48;2;0;175;0m▀▀\x1b[48;2;1;162;1m\x1b[38;2;0;176;0m▀\x1b[48;2;9;47;6m\x1b[38;2;6;95;4m▀\x1b[48;2;11;13;8m \x1b[48;2;11;15;8m\x1b[38;2;11;14;8m▀ \x1b[48;2;10;15;8m \x1b[48;2;21;13;7m\x1b[38;2;22;13;7m▀\x1b[48;2;114;2;1m\x1b[38;2;121;2;1m▀\x1b[48;2;164;0;0m\x1b[38;2;170;0;0m▀\x1b[48;2;127;0;0m\x1b[38;2;118;0;0m▀\x1b[m +\x1b[48;2;14;0;0m\x1b[38;2;147;0;0m▀\x1b[48;2;183;0;0m\x1b[38;2;108;0;0m▀\x1b[48;2;107;1;1m\x1b[38;2;114;1;1m▀\x1b[48;2;26;13;7m\x1b[38;2;27;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[38;2;11;14;7m▀ \x1b[48;2;10;14;7m\x1b[38;2;43;27;20m▀\x1b[48;2;9;14;7m\x1b[38;2;42;25;18m▀\x1b[48;2;11;14;7m\x1b[38;2;14;16;9m▀\x1b[48;2;11;15;8m\x1b[38;2;9;14;7m▀\x1b[38;2;10;14;7m▀\x1b[38;2;11;14;7m▀ \x1b[48;2;11;12;8m \x1b[48;2;9;49;6m\x1b[38;2;8;64;5m▀\x1b[48;2;1;166;1m\x1b[38;2;1;159;1m▀\x1b[48;2;0;175;0m\x1b[38;2;1;171;1m▀ \x1b[48;2;1;159;1m\x1b[38;2;1;167;1m▀\x1b[48;2;7;79;5m\x1b[38;2;4;122;3m▀\x1b[48;2;2;144;2m\x1b[38;2;2;158;1m▀\x1b[48;2;0;158;1m\x1b[38;2;0;177;0m▀\x1b[48;2;7;44;6m\x1b[38;2;4;112;3m▀\x1b[48;2;9;12;7m\x1b[38;2;11;17;7m▀\x1b[48;2;9;14;7m\x1b[38;2;11;14;8m▀\x1b[38;2;11;15;8m▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;11;14;7m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;20;13;7m\x1b[38;2;21;13;7m▀\x1b[48;2;102;2;1m\x1b[38;2;108;2;1m▀\x1b[48;2;121;0;0m\x1b[38;2;127;0;0m▀\x1b[48;2;146;0;0m\x1b[38;2;136;0;0m▀\x1b[m +\x1b[48;2;3;0;0m\x1b[38;2;137;0;0m▀\x1b[48;2;173;0;0m\x1b[38;2;50;0;0m▀\x1b[48;2;93;1;1m\x1b[38;2;100;1;1m▀\x1b[48;2;24;13;7m\x1b[38;2;25;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀▀\x1b[48;2;17;14;7m\x1b[38;2;11;14;8m▀\x1b[48;2;49;12;7m\x1b[38;2;9;24;7m▀\x1b[48;2;62;54;4m\x1b[38;2;8;133;2m▀\x1b[48;2;7;159;1m\x1b[38;2;2;176;0m▀\x1b[48;2;0;175;0m \x1b[48;2;1;172;1m\x1b[38;2;0;175;0m▀\x1b[48;2;1;159;1m\x1b[38;2;0;173;1m▀\x1b[48;2;46;122;19m\x1b[38;2;1;176;0m▀\x1b[48;2;122;63;45m\x1b[38;2;45;111;18m▀\x1b[48;2;135;52;49m\x1b[38;2;75;36;31m▀\x1b[48;2;135;53;49m\x1b[38;2;74;36;30m▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;136;53;49m\x1b[38;2;75;37;31m▀\x1b[48;2;119;49;45m\x1b[38;2;66;34;28m▀\x1b[48;2;25;20;13m\x1b[38;2;18;18;11m▀\x1b[48;2;10;14;7m \x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;19;13;7m \x1b[48;2;89;2;1m\x1b[38;2;95;2;1m▀\x1b[48;2;77;0;0m\x1b[38;2;83;0;0m▀\x1b[48;2;128;0;0m\x1b[38;2;119;0;0m▀\x1b[m +\x1b[48;2;60;0;0m\x1b[38;2;126;0;0m▀\x1b[48;2;182;0;0m\x1b[38;2;249;0;0m▀\x1b[48;2;83;1;1m\x1b[38;2;87;1;1m▀\x1b[48;2;22;13;7m\x1b[38;2;23;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;16;14;7m▀\x1b[48;2;14;14;7m\x1b[38;2;42;13;7m▀\x1b[48;2;58;13;6m\x1b[38;2;95;11;5m▀\x1b[48;2;34;13;7m\x1b[38;2;100;11;5m▀\x1b[48;2;9;14;7m\x1b[38;2;21;17;7m▀\x1b[48;2;11;12;8m\x1b[38;2;8;55;6m▀\x1b[38;2;7;75;5m▀\x1b[38;2;8;65;5m▀\x1b[48;2;11;13;8m\x1b[38;2;9;41;6m▀\x1b[48;2;12;15;8m\x1b[38;2;60;37;28m▀\x1b[38;2;90;42;37m▀\x1b[38;2;88;42;36m▀▀▀▀▀▀▀▀▀▀▀▀\x1b[38;2;89;42;37m▀\x1b[38;2;78;39;33m▀\x1b[48;2;11;15;8m\x1b[38;2;20;18;11m▀\x1b[48;2;11;14;7m\x1b[38;2;10;14;7m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;18;13;7m \x1b[48;2;78;2;1m\x1b[38;2;83;2;1m▀\x1b[48;2;196;0;0m\x1b[38;2;40;0;0m▀\x1b[48;2;217;0;0m\x1b[38;2;137;0;0m▀\x1b[m +\x1b[48;2;227;0;0m\x1b[38;2;16;0;0m▀\x1b[48;2;116;0;0m\x1b[38;2;21;0;0m▀\x1b[48;2;79;1;1m\x1b[38;2;81;1;1m▀\x1b[48;2;22;13;7m \x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[38;2;10;15;8m▀\x1b[48;2;10;15;8m\x1b[38;2;21;14;7m▀\x1b[48;2;11;15;8m\x1b[38;2;14;14;7m▀\x1b[38;2;11;14;7m▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \x1b[48;2;10;15;8m \x1b[48;2;17;13;7m\x1b[38;2;18;13;7m▀\x1b[48;2;75;2;1m\x1b[38;2;76;2;1m▀\x1b[48;2;97;0;0m\x1b[38;2;34;0;0m▀\x1b[48;2;76;0;0m\x1b[38;2;147;0;0m▀\x1b[m +\x1b[48;2;161;0;0m\x1b[38;2;183;0;0m▀\x1b[48;2;49;0;0m\x1b[38;2;211;0;0m▀\x1b[48;2;75;1;1m\x1b[38;2;77;1;1m▀\x1b[48;2;21;13;7m \x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;17;13;7m \x1b[48;2;71;2;1m\x1b[38;2;73;2;1m▀\x1b[48;2;253;0;0m\x1b[38;2;159;0;0m▀\x1b[48;2;191;0;0m\x1b[38;2;5;0;0m▀\x1b[m +\x1b[48;2;110;161;100m\x1b[38;2;116;0;0m▀\x1b[48;2;9;205;205m\x1b[38;2;192;0;0m▀\x1b[48;2;78;0;0m\x1b[38;2;77;1;0m▀\x1b[48;2;66;3;1m\x1b[38;2;30;11;6m▀\x1b[48;2;42;8;4m\x1b[38;2;9;15;8m▀\x1b[48;2;39;8;4m\x1b[38;2;10;15;8m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;40;8;4m▀\x1b[48;2;39;8;4m▀▀▀▀▀▀▀\x1b[48;2;40;8;4m▀▀▀\x1b[48;2;39;8;4m▀\x1b[48;2;40;8;4m▀\x1b[48;2;39;8;4m▀\x1b[48;2;41;8;4m\x1b[38;2;9;15;8m▀\x1b[48;2;62;4;2m\x1b[38;2;24;13;7m▀\x1b[48;2;78;0;0m\x1b[38;2;74;1;1m▀\x1b[48;2;221;222;0m\x1b[38;2;59;0;0m▀\x1b[48;2;67;199;133m\x1b[38;2;85;0;0m▀\x1b[m +\x1b[48;2;0;0;0m\x1b[38;2;143;233;149m▀\x1b[48;2;108;184;254m\x1b[38;2;213;6;76m▀\x1b[48;2;197;183;82m\x1b[38;2;76;0;0m▀\x1b[48;2;154;157;0m▀\x1b[48;2;96;0;0m▀\x1b[48;2;253;0;0m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;226;0;0m▀\x1b[48;2;255;127;255m▀\x1b[48;2;84;36;66m\x1b[38;2;64;247;251m▀\x1b[48;2;0;0;0m\x1b[38;2;18;76;210m▀\x1b[m +\x1b[48;2;0;0;0m \x1b[m +\x1b[48;2;0;0;0m \x1b[m +""" +) + +if __name__ == "__main__": + main() diff --git a/examples/full-screen/buttons.py b/examples/full-screen/buttons.py new file mode 100755 index 0000000..540194d --- /dev/null +++ b/examples/full-screen/buttons.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python +""" +A simple example of a few buttons and click handlers. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.layout import HSplit, Layout, VSplit +from prompt_toolkit.styles import Style +from prompt_toolkit.widgets import Box, Button, Frame, Label, TextArea + + +# Event handlers for all the buttons. +def button1_clicked(): + text_area.text = "Button 1 clicked" + + +def button2_clicked(): + text_area.text = "Button 2 clicked" + + +def button3_clicked(): + text_area.text = "Button 3 clicked" + + +def exit_clicked(): + get_app().exit() + + +# All the widgets for the UI. +button1 = Button("Button 1", handler=button1_clicked) +button2 = Button("Button 2", handler=button2_clicked) +button3 = Button("Button 3", handler=button3_clicked) +button4 = Button("Exit", handler=exit_clicked) +text_area = TextArea(focusable=True) + + +# Combine all the widgets in a UI. +# The `Box` object ensures that padding will be inserted around the containing +# widget. It adapts automatically, unless an explicit `padding` amount is given. +root_container = Box( + HSplit( + [ + Label(text="Press `Tab` to move the focus."), + VSplit( + [ + Box( + body=HSplit([button1, button2, button3, button4], padding=1), + padding=1, + style="class:left-pane", + ), + Box(body=Frame(text_area), padding=1, style="class:right-pane"), + ] + ), + ] + ), +) + +layout = Layout(container=root_container, focused_element=button1) + + +# Key bindings. +kb = KeyBindings() +kb.add("tab")(focus_next) +kb.add("s-tab")(focus_previous) + + +# Styling. +style = Style( + [ + ("left-pane", "bg:#888800 #000000"), + ("right-pane", "bg:#00aa00 #000000"), + ("button", "#000000"), + ("button-arrow", "#000000"), + ("button focused", "bg:#ff0000"), + ("text-area focused", "bg:#ff0000"), + ] +) + + +# Build a main application object. +application = Application(layout=layout, key_bindings=kb, style=style, full_screen=True) + + +def main(): + application.run() + + +if __name__ == "__main__": + main() diff --git a/examples/full-screen/calculator.py b/examples/full-screen/calculator.py new file mode 100755 index 0000000..1cb513f --- /dev/null +++ b/examples/full-screen/calculator.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python +""" +A simple example of a calculator program. +This could be used as inspiration for a REPL. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.document import Document +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, Window +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.styles import Style +from prompt_toolkit.widgets import SearchToolbar, TextArea + +help_text = """ +Type any expression (e.g. "4 + 4") followed by enter to execute. +Press Control-C to exit. +""" + + +def main(): + # The layout. + search_field = SearchToolbar() # For reverse search. + + output_field = TextArea(style="class:output-field", text=help_text) + input_field = TextArea( + height=1, + prompt=">>> ", + style="class:input-field", + multiline=False, + wrap_lines=False, + search_field=search_field, + ) + + container = HSplit( + [ + output_field, + Window(height=1, char="-", style="class:line"), + input_field, + search_field, + ] + ) + + # Attach accept handler to the input field. We do this by assigning the + # handler to the `TextArea` that we created earlier. it is also possible to + # pass it to the constructor of `TextArea`. + # NOTE: It's better to assign an `accept_handler`, rather then adding a + # custom ENTER key binding. This will automatically reset the input + # field and add the strings to the history. + def accept(buff): + # Evaluate "calculator" expression. + try: + output = f"\n\nIn: {input_field.text}\nOut: {eval(input_field.text)}" # Don't do 'eval' in real code! + except BaseException as e: + output = f"\n\n{e}" + new_text = output_field.text + output + + # Add text to output buffer. + output_field.buffer.document = Document( + text=new_text, cursor_position=len(new_text) + ) + + input_field.accept_handler = accept + + # The key bindings. + kb = KeyBindings() + + @kb.add("c-c") + @kb.add("c-q") + def _(event): + "Pressing Ctrl-Q or Ctrl-C will exit the user interface." + event.app.exit() + + # Style. + style = Style( + [ + ("output-field", "bg:#000044 #ffffff"), + ("input-field", "bg:#000000 #ffffff"), + ("line", "#004400"), + ] + ) + + # Run application. + application = Application( + layout=Layout(container, focused_element=input_field), + key_bindings=kb, + style=style, + mouse_support=True, + full_screen=True, + ) + + application.run() + + +if __name__ == "__main__": + main() diff --git a/examples/full-screen/dummy-app.py b/examples/full-screen/dummy-app.py new file mode 100755 index 0000000..7ea7506 --- /dev/null +++ b/examples/full-screen/dummy-app.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +""" +This is the most simple example possible. +""" +from prompt_toolkit import Application + +app = Application(full_screen=False) +app.run() diff --git a/examples/full-screen/full-screen-demo.py b/examples/full-screen/full-screen-demo.py new file mode 100755 index 0000000..de7379a --- /dev/null +++ b/examples/full-screen/full-screen-demo.py @@ -0,0 +1,225 @@ +#!/usr/bin/env python +""" +""" +from pygments.lexers.html import HtmlLexer + +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.layout.containers import Float, HSplit, VSplit +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.menus import CompletionsMenu +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.styles import Style +from prompt_toolkit.widgets import ( + Box, + Button, + Checkbox, + Dialog, + Frame, + Label, + MenuContainer, + MenuItem, + ProgressBar, + RadioList, + TextArea, +) + + +def accept_yes(): + get_app().exit(result=True) + + +def accept_no(): + get_app().exit(result=False) + + +def do_exit(): + get_app().exit(result=False) + + +yes_button = Button(text="Yes", handler=accept_yes) +no_button = Button(text="No", handler=accept_no) +textfield = TextArea(lexer=PygmentsLexer(HtmlLexer)) +checkbox1 = Checkbox(text="Checkbox") +checkbox2 = Checkbox(text="Checkbox") + +radios = RadioList( + values=[ + ("Red", "red"), + ("Green", "green"), + ("Blue", "blue"), + ("Orange", "orange"), + ("Yellow", "yellow"), + ("Purple", "Purple"), + ("Brown", "Brown"), + ] +) + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + +root_container = HSplit( + [ + VSplit( + [ + Frame(body=Label(text="Left frame\ncontent")), + Dialog(title="The custom window", body=Label("hello\ntest")), + textfield, + ], + height=D(), + ), + VSplit( + [ + Frame(body=ProgressBar(), title="Progress bar"), + Frame( + title="Checkbox list", + body=HSplit([checkbox1, checkbox2]), + ), + Frame(title="Radio list", body=radios), + ], + padding=1, + ), + Box( + body=VSplit([yes_button, no_button], align="CENTER", padding=3), + style="class:button-bar", + height=3, + ), + ] +) + +root_container = MenuContainer( + body=root_container, + menu_items=[ + MenuItem( + "File", + children=[ + MenuItem("New"), + MenuItem( + "Open", + children=[ + MenuItem("From file..."), + MenuItem("From URL..."), + MenuItem( + "Something else..", + children=[ + MenuItem("A"), + MenuItem("B"), + MenuItem("C"), + MenuItem("D"), + MenuItem("E"), + ], + ), + ], + ), + MenuItem("Save"), + MenuItem("Save as..."), + MenuItem("-", disabled=True), + MenuItem("Exit", handler=do_exit), + ], + ), + MenuItem( + "Edit", + children=[ + MenuItem("Undo"), + MenuItem("Cut"), + MenuItem("Copy"), + MenuItem("Paste"), + MenuItem("Delete"), + MenuItem("-", disabled=True), + MenuItem("Find"), + MenuItem("Find next"), + MenuItem("Replace"), + MenuItem("Go To"), + MenuItem("Select All"), + MenuItem("Time/Date"), + ], + ), + MenuItem("View", children=[MenuItem("Status Bar")]), + MenuItem("Info", children=[MenuItem("About")]), + ], + floats=[ + Float( + xcursor=True, + ycursor=True, + content=CompletionsMenu(max_height=16, scroll_offset=1), + ), + ], +) + +# Global key bindings. +bindings = KeyBindings() +bindings.add("tab")(focus_next) +bindings.add("s-tab")(focus_previous) + + +style = Style.from_dict( + { + "window.border": "#888888", + "shadow": "bg:#222222", + "menu-bar": "bg:#aaaaaa #888888", + "menu-bar.selected-item": "bg:#ffffff #000000", + "menu": "bg:#888888 #ffffff", + "menu.border": "#aaaaaa", + "window.border shadow": "#444444", + "focused button": "bg:#880000 #ffffff noinherit", + # Styling for Dialog widgets. + "button-bar": "bg:#aaaaff", + } +) + + +application = Application( + layout=Layout(root_container, focused_element=yes_button), + key_bindings=bindings, + style=style, + mouse_support=True, + full_screen=True, +) + + +def run(): + result = application.run() + print("You said: %r" % result) + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/hello-world.py b/examples/full-screen/hello-world.py new file mode 100755 index 0000000..b818018 --- /dev/null +++ b/examples/full-screen/hello-world.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +""" +A simple example of a a text area displaying "Hello World!". +""" +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout import Layout +from prompt_toolkit.widgets import Box, Frame, TextArea + +# Layout for displaying hello world. +# (The frame creates the border, the box takes care of the margin/padding.) +root_container = Box( + Frame( + TextArea( + text="Hello world!\nPress control-c to quit.", + width=40, + height=10, + ) + ), +) +layout = Layout(container=root_container) + + +# Key bindings. +kb = KeyBindings() + + +@kb.add("c-c") +def _(event): + "Quit when control-c is pressed." + event.app.exit() + + +# Build a main application object. +application = Application(layout=layout, key_bindings=kb, full_screen=True) + + +def main(): + application.run() + + +if __name__ == "__main__": + main() diff --git a/examples/full-screen/no-layout.py b/examples/full-screen/no-layout.py new file mode 100644 index 0000000..be5c6f8 --- /dev/null +++ b/examples/full-screen/no-layout.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +""" +An empty full screen application without layout. +""" +from prompt_toolkit import Application + +Application(full_screen=True).run() diff --git a/examples/full-screen/pager.py b/examples/full-screen/pager.py new file mode 100755 index 0000000..799c834 --- /dev/null +++ b/examples/full-screen/pager.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +""" +A simple application that shows a Pager application. +""" +from pygments.lexers.python import PythonLexer + +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.dimension import LayoutDimension as D +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.styles import Style +from prompt_toolkit.widgets import SearchToolbar, TextArea + +# Create one text buffer for the main content. + +_pager_py_path = __file__ + + +with open(_pager_py_path, "rb") as f: + text = f.read().decode("utf-8") + + +def get_statusbar_text(): + return [ + ("class:status", _pager_py_path + " - "), + ( + "class:status.position", + "{}:{}".format( + text_area.document.cursor_position_row + 1, + text_area.document.cursor_position_col + 1, + ), + ), + ("class:status", " - Press "), + ("class:status.key", "Ctrl-C"), + ("class:status", " to exit, "), + ("class:status.key", "/"), + ("class:status", " for searching."), + ] + + +search_field = SearchToolbar( + text_if_not_searching=[("class:not-searching", "Press '/' to start searching.")] +) + + +text_area = TextArea( + text=text, + read_only=True, + scrollbar=True, + line_numbers=True, + search_field=search_field, + lexer=PygmentsLexer(PythonLexer), +) + + +root_container = HSplit( + [ + # The top toolbar. + Window( + content=FormattedTextControl(get_statusbar_text), + height=D.exact(1), + style="class:status", + ), + # The main content. + text_area, + search_field, + ] +) + + +# Key bindings. +bindings = KeyBindings() + + +@bindings.add("c-c") +@bindings.add("q") +def _(event): + "Quit." + event.app.exit() + + +style = Style.from_dict( + { + "status": "reverse", + "status.position": "#aaaa00", + "status.key": "#ffaa00", + "not-searching": "#888888", + } +) + + +# create application. +application = Application( + layout=Layout(root_container, focused_element=text_area), + key_bindings=bindings, + enable_page_navigation_bindings=True, + mouse_support=True, + style=style, + full_screen=True, +) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/scrollable-panes/simple-example.py b/examples/full-screen/scrollable-panes/simple-example.py new file mode 100644 index 0000000..a94274f --- /dev/null +++ b/examples/full-screen/scrollable-panes/simple-example.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +""" +A simple example of a scrollable pane. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.layout import Dimension, HSplit, Layout, ScrollablePane +from prompt_toolkit.widgets import Frame, TextArea + + +def main(): + # Create a big layout of many text areas, then wrap them in a `ScrollablePane`. + root_container = Frame( + ScrollablePane( + HSplit( + [ + Frame(TextArea(text=f"label-{i}"), width=Dimension()) + for i in range(20) + ] + ) + ) + # ScrollablePane(HSplit([TextArea(text=f"label-{i}") for i in range(20)])) + ) + + layout = Layout(container=root_container) + + # Key bindings. + kb = KeyBindings() + + @kb.add("c-c") + def exit(event) -> None: + get_app().exit() + + kb.add("tab")(focus_next) + kb.add("s-tab")(focus_previous) + + # Create and run application. + application = Application(layout=layout, key_bindings=kb, full_screen=True) + application.run() + + +if __name__ == "__main__": + main() diff --git a/examples/full-screen/scrollable-panes/with-completion-menu.py b/examples/full-screen/scrollable-panes/with-completion-menu.py new file mode 100644 index 0000000..fba8d17 --- /dev/null +++ b/examples/full-screen/scrollable-panes/with-completion-menu.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +""" +A simple example of a scrollable pane. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.focus import focus_next, focus_previous +from prompt_toolkit.layout import ( + CompletionsMenu, + Float, + FloatContainer, + HSplit, + Layout, + ScrollablePane, + VSplit, +) +from prompt_toolkit.widgets import Frame, Label, TextArea + + +def main(): + # Create a big layout of many text areas, then wrap them in a `ScrollablePane`. + root_container = VSplit( + [ + Label(""), + HSplit( + [ + Label("ScrollContainer Demo"), + Frame( + ScrollablePane( + HSplit( + [ + Frame( + TextArea( + text=f"label-{i}", + completer=animal_completer, + ) + ) + for i in range(20) + ] + ) + ), + ), + ] + ), + ] + ) + + root_container = FloatContainer( + root_container, + floats=[ + Float( + xcursor=True, + ycursor=True, + content=CompletionsMenu(max_height=16, scroll_offset=1), + ), + ], + ) + + layout = Layout(container=root_container) + + # Key bindings. + kb = KeyBindings() + + @kb.add("c-c") + def exit(event) -> None: + get_app().exit() + + kb.add("tab")(focus_next) + kb.add("s-tab")(focus_previous) + + # Create and run application. + application = Application( + layout=layout, key_bindings=kb, full_screen=True, mouse_support=True + ) + application.run() + + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + + +if __name__ == "__main__": + main() diff --git a/examples/full-screen/simple-demos/alignment.py b/examples/full-screen/simple-demos/alignment.py new file mode 100755 index 0000000..b20b43d --- /dev/null +++ b/examples/full-screen/simple-demos/alignment.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +""" +Demo of the different Window alignment options. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, Window, WindowAlign +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout + +LIPSUM = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum +mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus.""" + +# 1. The layout + +left_text = '\nLeft aligned text. - (Press "q" to quit)\n\n' + LIPSUM +center_text = "Centered text.\n\n" + LIPSUM +right_text = "Right aligned text.\n\n" + LIPSUM + + +body = HSplit( + [ + Window(FormattedTextControl(left_text), align=WindowAlign.LEFT), + Window(height=1, char="-"), + Window(FormattedTextControl(center_text), align=WindowAlign.CENTER), + Window(height=1, char="-"), + Window(FormattedTextControl(right_text), align=WindowAlign.RIGHT), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/autocompletion.py b/examples/full-screen/simple-demos/autocompletion.py new file mode 100755 index 0000000..bcbb594 --- /dev/null +++ b/examples/full-screen/simple-demos/autocompletion.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +""" +An example of a BufferControl in a full screen layout that offers auto +completion. + +Important is to make sure that there is a `CompletionsMenu` in the layout, +otherwise the completions won't be visible. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import Float, FloatContainer, HSplit, Window +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.menus import CompletionsMenu + +# The completer. +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + + +# The layout +buff = Buffer(completer=animal_completer, complete_while_typing=True) + +body = FloatContainer( + content=HSplit( + [ + Window( + FormattedTextControl('Press "q" to quit.'), height=1, style="reverse" + ), + Window(BufferControl(buffer=buff)), + ] + ), + floats=[ + Float( + xcursor=True, + ycursor=True, + content=CompletionsMenu(max_height=16, scroll_offset=1), + ) + ], +) + + +# Key bindings +kb = KeyBindings() + + +@kb.add("q") +@kb.add("c-c") +def _(event): + "Quit application." + event.app.exit() + + +# The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/colorcolumn.py b/examples/full-screen/simple-demos/colorcolumn.py new file mode 100755 index 0000000..054aa44 --- /dev/null +++ b/examples/full-screen/simple-demos/colorcolumn.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +""" +Colorcolumn example. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import ColorColumn, HSplit, Window +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.layout import Layout + +LIPSUM = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum +mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus.""" + +# Create text buffers. +buff = Buffer() +buff.text = LIPSUM + +# 1. The layout +color_columns = [ + ColorColumn(50), + ColorColumn(80, style="bg:#ff0000"), + ColorColumn(10, style="bg:#ff0000"), +] + +body = HSplit( + [ + Window(FormattedTextControl('Press "q" to quit.'), height=1, style="reverse"), + Window(BufferControl(buffer=buff), colorcolumns=color_columns), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/cursorcolumn-cursorline.py b/examples/full-screen/simple-demos/cursorcolumn-cursorline.py new file mode 100755 index 0000000..505b3ee --- /dev/null +++ b/examples/full-screen/simple-demos/cursorcolumn-cursorline.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +""" +Cursorcolumn / cursorline example. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, Window +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.layout import Layout + +LIPSUM = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum +mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus.""" + +# Create text buffers. Cursorcolumn/cursorline are mostly combined with an +# (editable) text buffers, where the user can move the cursor. + +buff = Buffer() +buff.text = LIPSUM + +# 1. The layout +body = HSplit( + [ + Window(FormattedTextControl('Press "q" to quit.'), height=1, style="reverse"), + Window(BufferControl(buffer=buff), cursorcolumn=True, cursorline=True), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/float-transparency.py b/examples/full-screen/simple-demos/float-transparency.py new file mode 100755 index 0000000..4dc38fc --- /dev/null +++ b/examples/full-screen/simple-demos/float-transparency.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python +""" +Example of the 'transparency' attribute of `Window' when used in a Float. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import Float, FloatContainer, Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.widgets import Frame + +LIPSUM = " ".join( + ( + """Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Maecenas quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est +bibendum mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus. """ + * 100 + ).split() +) + + +# 1. The layout +left_text = HTML("transparent=False\n") +right_text = HTML("transparent=True") +quit_text = "Press 'q' to quit." + + +body = FloatContainer( + content=Window(FormattedTextControl(LIPSUM), wrap_lines=True), + floats=[ + # Important note: Wrapping the floating objects in a 'Frame' is + # only required for drawing the border around the + # floating text. We do it here to make the layout more + # obvious. + # Left float. + Float( + Frame(Window(FormattedTextControl(left_text), width=20, height=4)), + transparent=False, + left=0, + ), + # Right float. + Float( + Frame(Window(FormattedTextControl(right_text), width=20, height=4)), + transparent=True, + right=0, + ), + # Quit text. + Float( + Frame( + Window(FormattedTextControl(quit_text), width=18, height=1), + style="bg:#ff44ff #ffffff", + ), + top=1, + ), + ], +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/floats.py b/examples/full-screen/simple-demos/floats.py new file mode 100755 index 0000000..0d45be9 --- /dev/null +++ b/examples/full-screen/simple-demos/floats.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python +""" +Horizontal split example. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import Float, FloatContainer, Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.widgets import Frame + +LIPSUM = " ".join( + ( + """Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Maecenas quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est +bibendum mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus. """ + * 100 + ).split() +) + + +# 1. The layout +left_text = "Floating\nleft" +right_text = "Floating\nright" +top_text = "Floating\ntop" +bottom_text = "Floating\nbottom" +center_text = "Floating\ncenter" +quit_text = "Press 'q' to quit." + + +body = FloatContainer( + content=Window(FormattedTextControl(LIPSUM), wrap_lines=True), + floats=[ + # Important note: Wrapping the floating objects in a 'Frame' is + # only required for drawing the border around the + # floating text. We do it here to make the layout more + # obvious. + # Left float. + Float( + Frame( + Window(FormattedTextControl(left_text), width=10, height=2), + style="bg:#44ffff #ffffff", + ), + left=0, + ), + # Right float. + Float( + Frame( + Window(FormattedTextControl(right_text), width=10, height=2), + style="bg:#44ffff #ffffff", + ), + right=0, + ), + # Bottom float. + Float( + Frame( + Window(FormattedTextControl(bottom_text), width=10, height=2), + style="bg:#44ffff #ffffff", + ), + bottom=0, + ), + # Top float. + Float( + Frame( + Window(FormattedTextControl(top_text), width=10, height=2), + style="bg:#44ffff #ffffff", + ), + top=0, + ), + # Center float. + Float( + Frame( + Window(FormattedTextControl(center_text), width=10, height=2), + style="bg:#44ffff #ffffff", + ) + ), + # Quit text. + Float( + Frame( + Window(FormattedTextControl(quit_text), width=18, height=1), + style="bg:#ff44ff #ffffff", + ), + top=6, + ), + ], +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/focus.py b/examples/full-screen/simple-demos/focus.py new file mode 100755 index 0000000..9fe9b8f --- /dev/null +++ b/examples/full-screen/simple-demos/focus.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +""" +Demonstration of how to programmatically focus a certain widget. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.document import Document +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, VSplit, Window +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.layout import Layout + +# 1. The layout +top_text = ( + "Focus example.\n" + "[q] Quit [a] Focus left top [b] Right top [c] Left bottom [d] Right bottom." +) + +LIPSUM = """Lorem ipsum dolor sit amet, consectetur adipiscing elit. +Maecenas quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est +bibendum mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus. """ + + +left_top = Window(BufferControl(Buffer(document=Document(LIPSUM)))) +left_bottom = Window(BufferControl(Buffer(document=Document(LIPSUM)))) +right_top = Window(BufferControl(Buffer(document=Document(LIPSUM)))) +right_bottom = Window(BufferControl(Buffer(document=Document(LIPSUM)))) + + +body = HSplit( + [ + Window(FormattedTextControl(top_text), height=2, style="reverse"), + Window(height=1, char="-"), # Horizontal line in the middle. + VSplit([left_top, Window(width=1, char="|"), right_top]), + Window(height=1, char="-"), # Horizontal line in the middle. + VSplit([left_bottom, Window(width=1, char="|"), right_bottom]), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +@kb.add("a") +def _(event): + event.app.layout.focus(left_top) + + +@kb.add("b") +def _(event): + event.app.layout.focus(right_top) + + +@kb.add("c") +def _(event): + event.app.layout.focus(left_bottom) + + +@kb.add("d") +def _(event): + event.app.layout.focus(right_bottom) + + +@kb.add("tab") +def _(event): + event.app.layout.focus_next() + + +@kb.add("s-tab") +def _(event): + event.app.layout.focus_previous() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/horizontal-align.py b/examples/full-screen/simple-demos/horizontal-align.py new file mode 100755 index 0000000..bb0de12 --- /dev/null +++ b/examples/full-screen/simple-demos/horizontal-align.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python +""" +Horizontal align demo with HSplit. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import ( + HorizontalAlign, + HSplit, + VerticalAlign, + VSplit, + Window, + WindowAlign, +) +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.widgets import Frame + +TITLE = HTML( + """ HSplit HorizontalAlign example. + Press 'q' to quit.""" +) + +LIPSUM = """\ +Lorem ipsum dolor +sit amet, consectetur +adipiscing elit. +Maecenas quis +interdum enim.""" + + +# 1. The layout +body = HSplit( + [ + Frame( + Window(FormattedTextControl(TITLE), height=2), style="bg:#88ff88 #000000" + ), + HSplit( + [ + # Left alignment. + VSplit( + [ + Window( + FormattedTextControl(HTML("LEFT")), + width=10, + ignore_content_width=True, + style="bg:#ff3333 ansiblack", + align=WindowAlign.CENTER, + ), + VSplit( + [ + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + ], + padding=1, + padding_style="bg:#888888", + align=HorizontalAlign.LEFT, + height=5, + padding_char="|", + ), + ] + ), + # Center alignment. + VSplit( + [ + Window( + FormattedTextControl(HTML("CENTER")), + width=10, + ignore_content_width=True, + style="bg:#ff3333 ansiblack", + align=WindowAlign.CENTER, + ), + VSplit( + [ + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + ], + padding=1, + padding_style="bg:#888888", + align=HorizontalAlign.CENTER, + height=5, + padding_char="|", + ), + ] + ), + # Right alignment. + VSplit( + [ + Window( + FormattedTextControl(HTML("RIGHT")), + width=10, + ignore_content_width=True, + style="bg:#ff3333 ansiblack", + align=WindowAlign.CENTER, + ), + VSplit( + [ + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + Window( + FormattedTextControl(LIPSUM), + height=4, + style="bg:#444488", + ), + ], + padding=1, + padding_style="bg:#888888", + align=HorizontalAlign.RIGHT, + height=5, + padding_char="|", + ), + ] + ), + # Justify + VSplit( + [ + Window( + FormattedTextControl(HTML("JUSTIFY")), + width=10, + ignore_content_width=True, + style="bg:#ff3333 ansiblack", + align=WindowAlign.CENTER, + ), + VSplit( + [ + Window( + FormattedTextControl(LIPSUM), style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), style="bg:#444488" + ), + ], + padding=1, + padding_style="bg:#888888", + align=HorizontalAlign.JUSTIFY, + height=5, + padding_char="|", + ), + ] + ), + ], + padding=1, + padding_style="bg:#ff3333 #ffffff", + padding_char=".", + align=VerticalAlign.TOP, + ), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/horizontal-split.py b/examples/full-screen/simple-demos/horizontal-split.py new file mode 100755 index 0000000..0427e67 --- /dev/null +++ b/examples/full-screen/simple-demos/horizontal-split.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +Horizontal split example. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout + +# 1. The layout +left_text = "\nVertical-split example. Press 'q' to quit.\n\n(top pane.)" +right_text = "\n(bottom pane.)" + + +body = HSplit( + [ + Window(FormattedTextControl(left_text)), + Window(height=1, char="-"), # Horizontal line in the middle. + Window(FormattedTextControl(right_text)), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/line-prefixes.py b/examples/full-screen/simple-demos/line-prefixes.py new file mode 100755 index 0000000..b52cb48 --- /dev/null +++ b/examples/full-screen/simple-demos/line-prefixes.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python +""" +An example of a BufferControl in a full screen layout that offers auto +completion. + +Important is to make sure that there is a `CompletionsMenu` in the layout, +otherwise the completions won't be visible. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.filters import Condition +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import Float, FloatContainer, HSplit, Window +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.menus import CompletionsMenu + +LIPSUM = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum +mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus.""" + + +def get_line_prefix(lineno, wrap_count): + if wrap_count == 0: + return HTML('[%s] ') % lineno + + text = str(lineno) + "-" + "*" * (lineno // 2) + ": " + return HTML('[%s.%s] ') % ( + lineno, + wrap_count, + text, + ) + + +# Global wrap lines flag. +wrap_lines = True + + +# The layout +buff = Buffer(complete_while_typing=True) +buff.text = LIPSUM + + +body = FloatContainer( + content=HSplit( + [ + Window( + FormattedTextControl( + 'Press "q" to quit. Press "w" to enable/disable wrapping.' + ), + height=1, + style="reverse", + ), + Window( + BufferControl(buffer=buff), + get_line_prefix=get_line_prefix, + wrap_lines=Condition(lambda: wrap_lines), + ), + ] + ), + floats=[ + Float( + xcursor=True, + ycursor=True, + content=CompletionsMenu(max_height=16, scroll_offset=1), + ) + ], +) + + +# Key bindings +kb = KeyBindings() + + +@kb.add("q") +@kb.add("c-c") +def _(event): + "Quit application." + event.app.exit() + + +@kb.add("w") +def _(event): + "Disable/enable wrapping." + global wrap_lines + wrap_lines = not wrap_lines + + +# The `Application` +application = Application( + layout=Layout(body), key_bindings=kb, full_screen=True, mouse_support=True +) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/margins.py b/examples/full-screen/simple-demos/margins.py new file mode 100755 index 0000000..467492d --- /dev/null +++ b/examples/full-screen/simple-demos/margins.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python +""" +Example of Window margins. + +This is mainly used for displaying line numbers and scroll bars, but it could +be used to display any other kind of information as well. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, Window +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.margins import NumberedMargin, ScrollbarMargin + +LIPSUM = ( + """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum +mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat. In vel dictum ex, vulputate accumsan mi. Donec ut quam +placerat massa tempor elementum. Sed tristique mauris ac suscipit euismod. Ut +tempus vehicula augue non venenatis. Mauris aliquam velit turpis, nec congue +risus aliquam sit amet. Pellentesque blandit scelerisque felis, faucibus +consequat ante. Curabitur tempor tortor a imperdiet tincidunt. Nam sed justo +sit amet odio bibendum congue. Quisque varius ligula nec ligula gravida, sed +convallis augue faucibus. Nunc ornare pharetra bibendum. Praesent blandit ex +quis sodales maximus.""" + * 40 +) + +# Create text buffers. The margins will update if you scroll up or down. + +buff = Buffer() +buff.text = LIPSUM + +# 1. The layout +body = HSplit( + [ + Window(FormattedTextControl('Press "q" to quit.'), height=1, style="reverse"), + Window( + BufferControl(buffer=buff), + # Add margins. + left_margins=[NumberedMargin(), ScrollbarMargin()], + right_margins=[ScrollbarMargin(), ScrollbarMargin()], + ), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +@kb.add("c-c") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/vertical-align.py b/examples/full-screen/simple-demos/vertical-align.py new file mode 100755 index 0000000..1475d71 --- /dev/null +++ b/examples/full-screen/simple-demos/vertical-align.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python +""" +Vertical align demo with VSplit. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import ( + HSplit, + VerticalAlign, + VSplit, + Window, + WindowAlign, +) +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.widgets import Frame + +TITLE = HTML( + """ VSplit VerticalAlign example. + Press 'q' to quit.""" +) + +LIPSUM = """ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas +quis interdum enim. Nam viverra, mauris et blandit malesuada, ante est bibendum +mauris, ac dignissim dui tellus quis ligula. Aenean condimentum leo at +dignissim placerat.""" + +# 1. The layout +body = HSplit( + [ + Frame( + Window(FormattedTextControl(TITLE), height=2), style="bg:#88ff88 #000000" + ), + VSplit( + [ + Window( + FormattedTextControl(HTML(" VerticalAlign.TOP")), + height=4, + ignore_content_width=True, + style="bg:#ff3333 #000000 bold", + align=WindowAlign.CENTER, + ), + Window( + FormattedTextControl(HTML(" VerticalAlign.CENTER")), + height=4, + ignore_content_width=True, + style="bg:#ff3333 #000000 bold", + align=WindowAlign.CENTER, + ), + Window( + FormattedTextControl(HTML(" VerticalAlign.BOTTOM")), + height=4, + ignore_content_width=True, + style="bg:#ff3333 #000000 bold", + align=WindowAlign.CENTER, + ), + Window( + FormattedTextControl(HTML(" VerticalAlign.JUSTIFY")), + height=4, + ignore_content_width=True, + style="bg:#ff3333 #000000 bold", + align=WindowAlign.CENTER, + ), + ], + height=1, + padding=1, + padding_style="bg:#ff3333", + ), + VSplit( + [ + # Top alignment. + HSplit( + [ + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + ], + padding=1, + padding_style="bg:#888888", + align=VerticalAlign.TOP, + padding_char="~", + ), + # Center alignment. + HSplit( + [ + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + ], + padding=1, + padding_style="bg:#888888", + align=VerticalAlign.CENTER, + padding_char="~", + ), + # Bottom alignment. + HSplit( + [ + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + Window( + FormattedTextControl(LIPSUM), height=4, style="bg:#444488" + ), + ], + padding=1, + padding_style="bg:#888888", + align=VerticalAlign.BOTTOM, + padding_char="~", + ), + # Justify + HSplit( + [ + Window(FormattedTextControl(LIPSUM), style="bg:#444488"), + Window(FormattedTextControl(LIPSUM), style="bg:#444488"), + Window(FormattedTextControl(LIPSUM), style="bg:#444488"), + ], + padding=1, + padding_style="bg:#888888", + align=VerticalAlign.JUSTIFY, + padding_char="~", + ), + ], + padding=1, + padding_style="bg:#ff3333 #ffffff", + padding_char=".", + ), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/simple-demos/vertical-split.py b/examples/full-screen/simple-demos/vertical-split.py new file mode 100755 index 0000000..b48d106 --- /dev/null +++ b/examples/full-screen/simple-demos/vertical-split.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +Vertical split example. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import VSplit, Window +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.layout import Layout + +# 1. The layout +left_text = "\nVertical-split example. Press 'q' to quit.\n\n(left pane.)" +right_text = "\n(right pane.)" + + +body = VSplit( + [ + Window(FormattedTextControl(left_text)), + Window(width=1, char="|"), # Vertical line in the middle. + Window(FormattedTextControl(right_text)), + ] +) + + +# 2. Key bindings +kb = KeyBindings() + + +@kb.add("q") +def _(event): + "Quit application." + event.app.exit() + + +# 3. The `Application` +application = Application(layout=Layout(body), key_bindings=kb, full_screen=True) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/split-screen.py b/examples/full-screen/split-screen.py new file mode 100755 index 0000000..af5403e --- /dev/null +++ b/examples/full-screen/split-screen.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python +""" +Simple example of a full screen application with a vertical split. + +This will show a window on the left for user input. When the user types, the +reversed input is shown on the right. Pressing Ctrl-Q will quit the application. +""" +from prompt_toolkit.application import Application +from prompt_toolkit.buffer import Buffer +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import HSplit, VSplit, Window, WindowAlign +from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl +from prompt_toolkit.layout.layout import Layout + +# 3. Create the buffers +# ------------------ + +left_buffer = Buffer() +right_buffer = Buffer() + +# 1. First we create the layout +# -------------------------- + +left_window = Window(BufferControl(buffer=left_buffer)) +right_window = Window(BufferControl(buffer=right_buffer)) + + +body = VSplit( + [ + left_window, + # A vertical line in the middle. We explicitly specify the width, to make + # sure that the layout engine will not try to divide the whole width by + # three for all these windows. + Window(width=1, char="|", style="class:line"), + # Display the Result buffer on the right. + right_window, + ] +) + +# As a demonstration. Let's add a title bar to the top, displaying "Hello world". + +# somewhere, because usually the default key bindings include searching. (Press +# Ctrl-R.) It would be really annoying if the search key bindings are handled, +# but the user doesn't see any feedback. We will add the search toolbar to the +# bottom by using an HSplit. + + +def get_titlebar_text(): + return [ + ("class:title", " Hello world "), + ("class:title", " (Press [Ctrl-Q] to quit.)"), + ] + + +root_container = HSplit( + [ + # The titlebar. + Window( + height=1, + content=FormattedTextControl(get_titlebar_text), + align=WindowAlign.CENTER, + ), + # Horizontal separator. + Window(height=1, char="-", style="class:line"), + # The 'body', like defined above. + body, + ] +) + + +# 2. Adding key bindings +# -------------------- + +# As a demonstration, we will add just a ControlQ key binding to exit the +# application. Key bindings are registered in a +# `prompt_toolkit.key_bindings.registry.Registry` instance. We use the +# `load_default_key_bindings` utility function to create a registry that +# already contains the default key bindings. + +kb = KeyBindings() + +# Now add the Ctrl-Q binding. We have to pass `eager=True` here. The reason is +# that there is another key *sequence* that starts with Ctrl-Q as well. Yes, a +# key binding is linked to a sequence of keys, not necessarily one key. So, +# what happens if there is a key binding for the letter 'a' and a key binding +# for 'ab'. When 'a' has been pressed, nothing will happen yet. Because the +# next key could be a 'b', but it could as well be anything else. If it's a 'c' +# for instance, we'll handle the key binding for 'a' and then look for a key +# binding for 'c'. So, when there's a common prefix in a key binding sequence, +# prompt-toolkit will wait calling a handler, until we have enough information. + +# Now, There is an Emacs key binding for the [Ctrl-Q Any] sequence by default. +# Pressing Ctrl-Q followed by any other key will do a quoted insert. So to be +# sure that we won't wait for that key binding to match, but instead execute +# Ctrl-Q immediately, we can pass eager=True. (Don't make a habit of adding +# `eager=True` to all key bindings, but do it when it conflicts with another +# existing key binding, and you definitely want to override that behavior. + + +@kb.add("c-c", eager=True) +@kb.add("c-q", eager=True) +def _(event): + """ + Pressing Ctrl-Q or Ctrl-C will exit the user interface. + + Setting a return value means: quit the event loop that drives the user + interface and return this value from the `Application.run()` call. + + Note that Ctrl-Q does not work on all terminals. Sometimes it requires + executing `stty -ixon`. + """ + event.app.exit() + + +# Now we add an event handler that captures change events to the buffer on the +# left. If the text changes over there, we'll update the buffer on the right. + + +def default_buffer_changed(_): + """ + When the buffer on the left changes, update the buffer on + the right. We just reverse the text. + """ + right_buffer.text = left_buffer.text[::-1] + + +left_buffer.on_text_changed += default_buffer_changed + + +# 3. Creating an `Application` instance +# ---------------------------------- + +# This glues everything together. + +application = Application( + layout=Layout(root_container, focused_element=left_window), + key_bindings=kb, + # Let's add mouse support! + mouse_support=True, + # Using an alternate screen buffer means as much as: "run full screen". + # It switches the terminal to an alternate screen. + full_screen=True, +) + + +# 4. Run the application +# ------------------- + + +def run(): + # Run the interface. (This runs the event loop until Ctrl-Q is pressed.) + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/full-screen/text-editor.py b/examples/full-screen/text-editor.py new file mode 100755 index 0000000..9c0a414 --- /dev/null +++ b/examples/full-screen/text-editor.py @@ -0,0 +1,383 @@ +#!/usr/bin/env python +""" +A simple example of a Notepad-like text editor. +""" +import datetime +from asyncio import Future, ensure_future + +from prompt_toolkit.application import Application +from prompt_toolkit.application.current import get_app +from prompt_toolkit.completion import PathCompleter +from prompt_toolkit.filters import Condition +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.layout.containers import ( + ConditionalContainer, + Float, + HSplit, + VSplit, + Window, + WindowAlign, +) +from prompt_toolkit.layout.controls import FormattedTextControl +from prompt_toolkit.layout.dimension import D +from prompt_toolkit.layout.layout import Layout +from prompt_toolkit.layout.menus import CompletionsMenu +from prompt_toolkit.lexers import DynamicLexer, PygmentsLexer +from prompt_toolkit.search import start_search +from prompt_toolkit.styles import Style +from prompt_toolkit.widgets import ( + Button, + Dialog, + Label, + MenuContainer, + MenuItem, + SearchToolbar, + TextArea, +) + + +class ApplicationState: + """ + Application state. + + For the simplicity, we store this as a global, but better would be to + instantiate this as an object and pass at around. + """ + + show_status_bar = True + current_path = None + + +def get_statusbar_text(): + return " Press Ctrl-C to open menu. " + + +def get_statusbar_right_text(): + return " {}:{} ".format( + text_field.document.cursor_position_row + 1, + text_field.document.cursor_position_col + 1, + ) + + +search_toolbar = SearchToolbar() +text_field = TextArea( + lexer=DynamicLexer( + lambda: PygmentsLexer.from_filename( + ApplicationState.current_path or ".txt", sync_from_start=False + ) + ), + scrollbar=True, + line_numbers=True, + search_field=search_toolbar, +) + + +class TextInputDialog: + def __init__(self, title="", label_text="", completer=None): + self.future = Future() + + def accept_text(buf): + get_app().layout.focus(ok_button) + buf.complete_state = None + return True + + def accept(): + self.future.set_result(self.text_area.text) + + def cancel(): + self.future.set_result(None) + + self.text_area = TextArea( + completer=completer, + multiline=False, + width=D(preferred=40), + accept_handler=accept_text, + ) + + ok_button = Button(text="OK", handler=accept) + cancel_button = Button(text="Cancel", handler=cancel) + + self.dialog = Dialog( + title=title, + body=HSplit([Label(text=label_text), self.text_area]), + buttons=[ok_button, cancel_button], + width=D(preferred=80), + modal=True, + ) + + def __pt_container__(self): + return self.dialog + + +class MessageDialog: + def __init__(self, title, text): + self.future = Future() + + def set_done(): + self.future.set_result(None) + + ok_button = Button(text="OK", handler=(lambda: set_done())) + + self.dialog = Dialog( + title=title, + body=HSplit([Label(text=text)]), + buttons=[ok_button], + width=D(preferred=80), + modal=True, + ) + + def __pt_container__(self): + return self.dialog + + +body = HSplit( + [ + text_field, + search_toolbar, + ConditionalContainer( + content=VSplit( + [ + Window( + FormattedTextControl(get_statusbar_text), style="class:status" + ), + Window( + FormattedTextControl(get_statusbar_right_text), + style="class:status.right", + width=9, + align=WindowAlign.RIGHT, + ), + ], + height=1, + ), + filter=Condition(lambda: ApplicationState.show_status_bar), + ), + ] +) + + +# Global key bindings. +bindings = KeyBindings() + + +@bindings.add("c-c") +def _(event): + "Focus menu." + event.app.layout.focus(root_container.window) + + +# +# Handlers for menu items. +# + + +def do_open_file(): + async def coroutine(): + open_dialog = TextInputDialog( + title="Open file", + label_text="Enter the path of a file:", + completer=PathCompleter(), + ) + + path = await show_dialog_as_float(open_dialog) + ApplicationState.current_path = path + + if path is not None: + try: + with open(path, "rb") as f: + text_field.text = f.read().decode("utf-8", errors="ignore") + except OSError as e: + show_message("Error", f"{e}") + + ensure_future(coroutine()) + + +def do_about(): + show_message("About", "Text editor demo.\nCreated by Jonathan Slenders.") + + +def show_message(title, text): + async def coroutine(): + dialog = MessageDialog(title, text) + await show_dialog_as_float(dialog) + + ensure_future(coroutine()) + + +async def show_dialog_as_float(dialog): + "Coroutine." + float_ = Float(content=dialog) + root_container.floats.insert(0, float_) + + app = get_app() + + focused_before = app.layout.current_window + app.layout.focus(dialog) + result = await dialog.future + app.layout.focus(focused_before) + + if float_ in root_container.floats: + root_container.floats.remove(float_) + + return result + + +def do_new_file(): + text_field.text = "" + + +def do_exit(): + get_app().exit() + + +def do_time_date(): + text = datetime.datetime.now().isoformat() + text_field.buffer.insert_text(text) + + +def do_go_to(): + async def coroutine(): + dialog = TextInputDialog(title="Go to line", label_text="Line number:") + + line_number = await show_dialog_as_float(dialog) + + try: + line_number = int(line_number) + except ValueError: + show_message("Invalid line number") + else: + text_field.buffer.cursor_position = ( + text_field.buffer.document.translate_row_col_to_index( + line_number - 1, 0 + ) + ) + + ensure_future(coroutine()) + + +def do_undo(): + text_field.buffer.undo() + + +def do_cut(): + data = text_field.buffer.cut_selection() + get_app().clipboard.set_data(data) + + +def do_copy(): + data = text_field.buffer.copy_selection() + get_app().clipboard.set_data(data) + + +def do_delete(): + text_field.buffer.cut_selection() + + +def do_find(): + start_search(text_field.control) + + +def do_find_next(): + search_state = get_app().current_search_state + + cursor_position = text_field.buffer.get_search_position( + search_state, include_current_position=False + ) + text_field.buffer.cursor_position = cursor_position + + +def do_paste(): + text_field.buffer.paste_clipboard_data(get_app().clipboard.get_data()) + + +def do_select_all(): + text_field.buffer.cursor_position = 0 + text_field.buffer.start_selection() + text_field.buffer.cursor_position = len(text_field.buffer.text) + + +def do_status_bar(): + ApplicationState.show_status_bar = not ApplicationState.show_status_bar + + +# +# The menu container. +# + + +root_container = MenuContainer( + body=body, + menu_items=[ + MenuItem( + "File", + children=[ + MenuItem("New...", handler=do_new_file), + MenuItem("Open...", handler=do_open_file), + MenuItem("Save"), + MenuItem("Save as..."), + MenuItem("-", disabled=True), + MenuItem("Exit", handler=do_exit), + ], + ), + MenuItem( + "Edit", + children=[ + MenuItem("Undo", handler=do_undo), + MenuItem("Cut", handler=do_cut), + MenuItem("Copy", handler=do_copy), + MenuItem("Paste", handler=do_paste), + MenuItem("Delete", handler=do_delete), + MenuItem("-", disabled=True), + MenuItem("Find", handler=do_find), + MenuItem("Find next", handler=do_find_next), + MenuItem("Replace"), + MenuItem("Go To", handler=do_go_to), + MenuItem("Select All", handler=do_select_all), + MenuItem("Time/Date", handler=do_time_date), + ], + ), + MenuItem( + "View", + children=[MenuItem("Status Bar", handler=do_status_bar)], + ), + MenuItem( + "Info", + children=[MenuItem("About", handler=do_about)], + ), + ], + floats=[ + Float( + xcursor=True, + ycursor=True, + content=CompletionsMenu(max_height=16, scroll_offset=1), + ), + ], + key_bindings=bindings, +) + + +style = Style.from_dict( + { + "status": "reverse", + "shadow": "bg:#440044", + } +) + + +layout = Layout(root_container, focused_element=text_field) + + +application = Application( + layout=layout, + enable_page_navigation_bindings=True, + style=style, + mouse_support=True, + full_screen=True, +) + + +def run(): + application.run() + + +if __name__ == "__main__": + run() diff --git a/examples/gevent-get-input.py b/examples/gevent-get-input.py new file mode 100755 index 0000000..ecb89b4 --- /dev/null +++ b/examples/gevent-get-input.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python +""" +For testing: test to make sure that everything still works when gevent monkey +patches are applied. +""" +from gevent.monkey import patch_all + +from prompt_toolkit.eventloop.defaults import create_event_loop +from prompt_toolkit.shortcuts import PromptSession + +if __name__ == "__main__": + # Apply patches. + patch_all() + + # There were some issues in the past when the event loop had an input hook. + def dummy_inputhook(*a): + pass + + eventloop = create_event_loop(inputhook=dummy_inputhook) + + # Ask for input. + session = PromptSession("Give me some input: ", loop=eventloop) + answer = session.prompt() + print("You said: %s" % answer) diff --git a/examples/print-text/ansi-colors.py b/examples/print-text/ansi-colors.py new file mode 100755 index 0000000..7bd3831 --- /dev/null +++ b/examples/print-text/ansi-colors.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python +""" +Demonstration of all the ANSI colors. +""" +from prompt_toolkit import print_formatted_text +from prompt_toolkit.formatted_text import HTML, FormattedText + +print = print_formatted_text + + +def main(): + wide_space = ("", " ") + space = ("", " ") + + print(HTML("\nForeground colors")) + print( + FormattedText( + [ + ("ansiblack", "ansiblack"), + wide_space, + ("ansired", "ansired"), + wide_space, + ("ansigreen", "ansigreen"), + wide_space, + ("ansiyellow", "ansiyellow"), + wide_space, + ("ansiblue", "ansiblue"), + wide_space, + ("ansimagenta", "ansimagenta"), + wide_space, + ("ansicyan", "ansicyan"), + wide_space, + ("ansigray", "ansigray"), + wide_space, + ("", "\n"), + ("ansibrightblack", "ansibrightblack"), + space, + ("ansibrightred", "ansibrightred"), + space, + ("ansibrightgreen", "ansibrightgreen"), + space, + ("ansibrightyellow", "ansibrightyellow"), + space, + ("ansibrightblue", "ansibrightblue"), + space, + ("ansibrightmagenta", "ansibrightmagenta"), + space, + ("ansibrightcyan", "ansibrightcyan"), + space, + ("ansiwhite", "ansiwhite"), + space, + ] + ) + ) + + print(HTML("\nBackground colors")) + print( + FormattedText( + [ + ("bg:ansiblack ansiwhite", "ansiblack"), + wide_space, + ("bg:ansired", "ansired"), + wide_space, + ("bg:ansigreen", "ansigreen"), + wide_space, + ("bg:ansiyellow", "ansiyellow"), + wide_space, + ("bg:ansiblue ansiwhite", "ansiblue"), + wide_space, + ("bg:ansimagenta", "ansimagenta"), + wide_space, + ("bg:ansicyan", "ansicyan"), + wide_space, + ("bg:ansigray", "ansigray"), + wide_space, + ("", "\n"), + ("bg:ansibrightblack", "ansibrightblack"), + space, + ("bg:ansibrightred", "ansibrightred"), + space, + ("bg:ansibrightgreen", "ansibrightgreen"), + space, + ("bg:ansibrightyellow", "ansibrightyellow"), + space, + ("bg:ansibrightblue", "ansibrightblue"), + space, + ("bg:ansibrightmagenta", "ansibrightmagenta"), + space, + ("bg:ansibrightcyan", "ansibrightcyan"), + space, + ("bg:ansiwhite", "ansiwhite"), + space, + ] + ) + ) + print() + + +if __name__ == "__main__": + main() diff --git a/examples/print-text/ansi.py b/examples/print-text/ansi.py new file mode 100755 index 0000000..618775c --- /dev/null +++ b/examples/print-text/ansi.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +""" +Demonstration of how to print using ANSI escape sequences. + +The advantage here is that this is cross platform. The escape sequences will be +parsed and turned into appropriate Win32 API calls on Windows. +""" +from prompt_toolkit import print_formatted_text +from prompt_toolkit.formatted_text import ANSI, HTML + +print = print_formatted_text + + +def title(text): + print(HTML("\n{}").format(text)) + + +def main(): + title("Special formatting") + print(ANSI(" \x1b[1mBold")) + print(ANSI(" \x1b[6mBlink")) + print(ANSI(" \x1b[3mItalic")) + print(ANSI(" \x1b[7mReverse")) + print(ANSI(" \x1b[4mUnderline")) + print(ANSI(" \x1b[9mStrike")) + print(ANSI(" \x1b[8mHidden\x1b[0m (Hidden)")) + + # Ansi colors. + title("ANSI colors") + + print(ANSI(" \x1b[91mANSI Red")) + print(ANSI(" \x1b[94mANSI Blue")) + + # Other named colors. + title("Named colors") + + print(ANSI(" \x1b[38;5;214morange")) + print(ANSI(" \x1b[38;5;90mpurple")) + + # Background colors. + title("Background colors") + + print(ANSI(" \x1b[97;101mANSI Red")) + print(ANSI(" \x1b[97;104mANSI Blue")) + + print() + + +if __name__ == "__main__": + main() diff --git a/examples/print-text/html.py b/examples/print-text/html.py new file mode 100755 index 0000000..5276fe3 --- /dev/null +++ b/examples/print-text/html.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +""" +Demonstration of how to print using the HTML class. +""" +from prompt_toolkit import HTML, print_formatted_text + +print = print_formatted_text + + +def title(text): + print(HTML("\n{}").format(text)) + + +def main(): + title("Special formatting") + print(HTML(" Bold")) + print(HTML(" Blink")) + print(HTML(" Italic")) + print(HTML(" Reverse")) + print(HTML(" Underline")) + print(HTML(" Strike")) + print(HTML(" Hidden (hidden)")) + + # Ansi colors. + title("ANSI colors") + + print(HTML(" ANSI Red")) + print(HTML(" ANSI Blue")) + + # Other named colors. + title("Named colors") + + print(HTML(" orange")) + print(HTML(" purple")) + + # Background colors. + title("Background colors") + + print(HTML(' ')) + print(HTML(' ')) + + # Interpolation. + title("HTML interpolation (see source)") + + print(HTML(" {}").format("")) + print(HTML(" {text}").format(text="")) + print(HTML(" %s") % ("",)) + + print() + + +if __name__ == "__main__": + main() diff --git a/examples/print-text/named-colors.py b/examples/print-text/named-colors.py new file mode 100755 index 0000000..ea3f0ba --- /dev/null +++ b/examples/print-text/named-colors.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +""" +Demonstration of all the ANSI colors. +""" +from prompt_toolkit import HTML, print_formatted_text +from prompt_toolkit.formatted_text import FormattedText +from prompt_toolkit.output import ColorDepth +from prompt_toolkit.styles.named_colors import NAMED_COLORS + +print = print_formatted_text + + +def main(): + tokens = FormattedText([("fg:" + name, name + " ") for name in NAMED_COLORS]) + + print(HTML("\nNamed colors, using 16 color output.")) + print("(Note that it doesn't really make sense to use named colors ") + print("with only 16 color output.)") + print(tokens, color_depth=ColorDepth.DEPTH_4_BIT) + + print(HTML("\nNamed colors, use 256 colors.")) + print(tokens) + + print(HTML("\nNamed colors, using True color output.")) + print(tokens, color_depth=ColorDepth.TRUE_COLOR) + + +if __name__ == "__main__": + main() diff --git a/examples/print-text/print-formatted-text.py b/examples/print-text/print-formatted-text.py new file mode 100755 index 0000000..4b09ae2 --- /dev/null +++ b/examples/print-text/print-formatted-text.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +Example of printing colored text to the output. +""" +from prompt_toolkit import print_formatted_text +from prompt_toolkit.formatted_text import ANSI, HTML, FormattedText +from prompt_toolkit.styles import Style + +print = print_formatted_text + + +def main(): + style = Style.from_dict( + { + "hello": "#ff0066", + "world": "#44ff44 italic", + } + ) + + # Print using a a list of text fragments. + text_fragments = FormattedText( + [ + ("class:hello", "Hello "), + ("class:world", "World"), + ("", "\n"), + ] + ) + print(text_fragments, style=style) + + # Print using an HTML object. + print(HTML("hello world\n"), style=style) + + # Print using an HTML object with inline styling. + print( + HTML( + ' ' + '\n' + ) + ) + + # Print using ANSI escape sequences. + print(ANSI("\x1b[31mhello \x1b[32mworld\n")) + + +if __name__ == "__main__": + main() diff --git a/examples/print-text/print-frame.py b/examples/print-text/print-frame.py new file mode 100755 index 0000000..fb703c5 --- /dev/null +++ b/examples/print-text/print-frame.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +""" +Example usage of 'print_container', a tool to print +any layout in a non-interactive way. +""" +from prompt_toolkit.shortcuts import print_container +from prompt_toolkit.widgets import Frame, TextArea + +print_container( + Frame( + TextArea(text="Hello world!\n"), + title="Stage: parse", + ) +) diff --git a/examples/print-text/prompt-toolkit-logo-ansi-art.py b/examples/print-text/prompt-toolkit-logo-ansi-art.py new file mode 100755 index 0000000..617a506 --- /dev/null +++ b/examples/print-text/prompt-toolkit-logo-ansi-art.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +r""" +This prints the prompt_toolkit logo at the terminal. +The ANSI output was generated using "pngtoansi": https://github.com/crgimenes/pngtoansi +(ESC still had to be replaced with \x1b +""" +from prompt_toolkit import print_formatted_text +from prompt_toolkit.formatted_text import ANSI + +print_formatted_text( + ANSI( + """ +\x1b[48;2;0;0;0m \x1b[m +\x1b[48;2;0;0;0m \x1b[48;2;0;249;0m\x1b[38;2;0;0;0m▀\x1b[48;2;0;209;0m▀\x1b[48;2;0;207;0m\x1b[38;2;6;34;6m▀\x1b[48;2;0;66;0m\x1b[38;2;30;171;30m▀\x1b[48;2;0;169;0m\x1b[38;2;51;35;51m▀\x1b[48;2;0;248;0m\x1b[38;2;49;194;49m▀\x1b[48;2;0;111;0m\x1b[38;2;25;57;25m▀\x1b[48;2;140;195;140m\x1b[38;2;3;17;3m▀\x1b[48;2;30;171;30m\x1b[38;2;0;0;0m▀\x1b[48;2;0;0;0m \x1b[m +\x1b[48;2;0;0;0m \x1b[48;2;77;127;78m\x1b[38;2;118;227;108m▀\x1b[48;2;216;1;13m\x1b[38;2;49;221;57m▀\x1b[48;2;26;142;76m\x1b[38;2;108;146;165m▀\x1b[48;2;26;142;90m\x1b[38;2;209;197;114m▀▀\x1b[38;2;209;146;114m▀\x1b[48;2;26;128;90m\x1b[38;2;158;197;114m▀\x1b[48;2;58;210;70m\x1b[38;2;223;152;89m▀\x1b[48;2;232;139;44m\x1b[38;2;97;121;146m▀\x1b[48;2;233;139;45m\x1b[38;2;140;188;183m▀\x1b[48;2;231;139;44m\x1b[38;2;40;168;8m▀\x1b[48;2;228;140;44m\x1b[38;2;37;169;7m▀\x1b[48;2;227;140;44m\x1b[38;2;36;169;7m▀\x1b[48;2;211;142;41m\x1b[38;2;23;171;5m▀\x1b[48;2;86;161;17m\x1b[38;2;2;174;1m▀\x1b[48;2;0;175;0m \x1b[48;2;0;254;0m\x1b[38;2;190;119;190m▀\x1b[48;2;92;39;23m\x1b[38;2;125;50;114m▀\x1b[48;2;43;246;41m\x1b[38;2;49;10;165m▀\x1b[48;2;12;128;90m\x1b[38;2;209;197;114m▀\x1b[48;2;26;128;90m▀▀▀▀\x1b[48;2;26;128;76m▀\x1b[48;2;26;128;90m\x1b[38;2;209;247;114m▀▀\x1b[38;2;209;197;114m▀\x1b[48;2;26;128;76m\x1b[38;2;209;247;114m▀\x1b[48;2;26;128;90m▀▀▀\x1b[48;2;26;128;76m▀\x1b[48;2;26;128;90m▀▀\x1b[48;2;12;128;76m▀\x1b[48;2;12;113;90m\x1b[38;2;209;247;64m▀\x1b[38;2;209;247;114m▀\x1b[48;2;12;128;90m▀\x1b[48;2;12;113;90m▀\x1b[48;2;12;113;76m\x1b[38;2;209;247;64m▀\x1b[48;2;12;128;90m▀\x1b[48;2;12;113;90m▀\x1b[48;2;12;113;76m\x1b[38;2;209;247;114m▀\x1b[48;2;12;113;90m\x1b[38;2;209;247;64m▀\x1b[48;2;26;128;90m\x1b[38;2;151;129;163m▀\x1b[48;2;115;120;103m\x1b[38;2;62;83;227m▀\x1b[48;2;138;14;25m\x1b[38;2;104;106;160m▀\x1b[48;2;0;0;57m\x1b[38;2;0;0;0m▀\x1b[m +\x1b[48;2;249;147;8m\x1b[38;2;172;69;38m▀\x1b[48;2;197;202;10m\x1b[38;2;82;192;58m▀\x1b[48;2;248;124;45m\x1b[38;2;251;131;47m▀\x1b[48;2;248;124;44m▀\x1b[48;2;248;124;45m▀▀\x1b[48;2;248;124;44m▀\x1b[48;2;248;124;45m▀\x1b[48;2;248;125;45m\x1b[38;2;251;130;47m▀\x1b[48;2;248;124;45m\x1b[38;2;252;130;47m▀\x1b[48;2;248;125;45m\x1b[38;2;252;131;47m▀\x1b[38;2;252;130;47m▀\x1b[38;2;252;131;47m▀▀\x1b[48;2;249;125;45m\x1b[38;2;255;130;48m▀\x1b[48;2;233;127;42m\x1b[38;2;190;141;35m▀\x1b[48;2;57;163;10m\x1b[38;2;13;172;3m▀\x1b[48;2;0;176;0m\x1b[38;2;0;175;0m▀\x1b[48;2;7;174;1m\x1b[38;2;35;169;7m▀\x1b[48;2;178;139;32m\x1b[38;2;220;136;41m▀\x1b[48;2;252;124;45m\x1b[38;2;253;131;47m▀\x1b[48;2;248;125;45m\x1b[38;2;251;131;47m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;248;125;44m▀\x1b[48;2;248;135;61m\x1b[38;2;251;132;48m▀\x1b[48;2;250;173;122m\x1b[38;2;251;133;50m▀\x1b[48;2;249;155;93m\x1b[38;2;251;132;49m▀\x1b[48;2;248;132;55m\x1b[38;2;251;132;48m▀\x1b[48;2;250;173;122m\x1b[38;2;251;134;51m▀\x1b[48;2;250;163;106m\x1b[38;2;251;134;50m▀\x1b[48;2;248;128;49m\x1b[38;2;251;132;47m▀\x1b[48;2;250;166;110m\x1b[38;2;251;135;52m▀\x1b[48;2;250;175;125m\x1b[38;2;251;136;54m▀\x1b[48;2;248;132;56m\x1b[38;2;251;132;48m▀\x1b[48;2;248;220;160m\x1b[38;2;105;247;172m▀\x1b[48;2;62;101;236m\x1b[38;2;11;207;160m▀\x1b[m +\x1b[48;2;138;181;197m\x1b[38;2;205;36;219m▀\x1b[48;2;177;211;200m\x1b[38;2;83;231;105m▀\x1b[48;2;242;113;40m\x1b[38;2;245;119;42m▀\x1b[48;2;243;113;41m▀\x1b[48;2;245;114;41m▀▀▀▀▀▀▀▀\x1b[38;2;245;119;43m▀▀▀\x1b[48;2;247;114;41m\x1b[38;2;246;119;43m▀\x1b[48;2;202;125;34m\x1b[38;2;143;141;25m▀\x1b[48;2;84;154;14m\x1b[38;2;97;152;17m▀\x1b[48;2;36;166;6m▀\x1b[48;2;139;140;23m\x1b[38;2;183;133;32m▀\x1b[48;2;248;114;41m\x1b[38;2;248;118;43m▀\x1b[48;2;245;115;41m\x1b[38;2;245;119;43m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[38;2;245;119;42m▀\x1b[48;2;246;117;44m\x1b[38;2;246;132;62m▀\x1b[48;2;246;123;54m\x1b[38;2;249;180;138m▀\x1b[48;2;246;120;49m\x1b[38;2;247;157;102m▀\x1b[48;2;246;116;42m\x1b[38;2;246;127;54m▀\x1b[48;2;246;121;50m\x1b[38;2;248;174;128m▀\x1b[48;2;246;120;48m\x1b[38;2;248;162;110m▀\x1b[48;2;246;116;41m\x1b[38;2;245;122;47m▀\x1b[48;2;246;118;46m\x1b[38;2;248;161;108m▀\x1b[48;2;244;118;47m\x1b[38;2;248;171;123m▀\x1b[48;2;243;115;42m\x1b[38;2;246;127;54m▀\x1b[48;2;179;52;29m\x1b[38;2;86;152;223m▀\x1b[48;2;141;225;95m\x1b[38;2;247;146;130m▀\x1b[m +\x1b[48;2;50;237;108m\x1b[38;2;94;70;153m▀\x1b[48;2;206;221;133m\x1b[38;2;64;240;39m▀\x1b[48;2;233;100;36m\x1b[38;2;240;107;38m▀\x1b[48;2;114;56;22m\x1b[38;2;230;104;37m▀\x1b[48;2;24;20;10m\x1b[38;2;193;90;33m▀\x1b[48;2;21;19;9m\x1b[38;2;186;87;32m▀▀▀▀▀▀▀\x1b[38;2;186;87;33m▀▀▀\x1b[48;2;22;18;10m\x1b[38;2;189;86;33m▀\x1b[48;2;18;36;8m\x1b[38;2;135;107;24m▀\x1b[48;2;3;153;2m\x1b[38;2;5;171;1m▀\x1b[48;2;0;177;0m \x1b[48;2;4;158;2m\x1b[38;2;69;147;12m▀\x1b[48;2;19;45;8m\x1b[38;2;185;89;32m▀\x1b[48;2;22;17;10m\x1b[38;2;186;87;33m▀\x1b[48;2;21;19;9m▀▀▀▀▀▀▀▀\x1b[48;2;21;19;10m▀▀\x1b[48;2;21;19;9m▀▀▀▀\x1b[48;2;21;19;10m▀▀▀\x1b[38;2;186;87;32m▀▀\x1b[48;2;21;19;9m\x1b[38;2;186;87;33m▀\x1b[48;2;21;19;10m\x1b[38;2;186;87;32m▀▀\x1b[48;2;21;19;9m\x1b[38;2;186;87;33m▀\x1b[48;2;22;19;10m\x1b[38;2;191;89;33m▀\x1b[48;2;95;49;20m\x1b[38;2;226;103;37m▀\x1b[48;2;227;99;36m\x1b[38;2;241;109;39m▀\x1b[48;2;80;140;154m\x1b[38;2;17;240;92m▀\x1b[48;2;221;58;175m\x1b[38;2;71;14;245m▀\x1b[m +\x1b[48;2;195;38;42m\x1b[38;2;5;126;86m▀\x1b[48;2;139;230;67m\x1b[38;2;253;201;228m▀\x1b[48;2;208;82;30m\x1b[38;2;213;89;32m▀\x1b[48;2;42;26;12m\x1b[38;2;44;27;12m▀\x1b[48;2;9;14;7m\x1b[38;2;8;13;7m▀\x1b[48;2;11;15;8m\x1b[38;2;10;14;7m▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;11;12;8m\x1b[38;2;10;17;7m▀\x1b[48;2;7;71;5m\x1b[38;2;4;120;3m▀\x1b[48;2;1;164;1m\x1b[38;2;0;178;0m▀\x1b[48;2;4;118;3m\x1b[38;2;0;177;0m▀\x1b[48;2;5;108;3m\x1b[38;2;4;116;3m▀\x1b[48;2;7;75;5m\x1b[38;2;10;23;7m▀\x1b[48;2;10;33;7m\x1b[38;2;10;12;7m▀\x1b[48;2;11;13;8m\x1b[38;2;10;14;7m▀\x1b[48;2;11;14;8m▀\x1b[48;2;11;15;8m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;10;14;7m\x1b[38;2;9;14;7m▀\x1b[48;2;30;21;10m\x1b[38;2;30;22;10m▀\x1b[48;2;195;79;29m\x1b[38;2;200;84;31m▀\x1b[48;2;205;228;23m\x1b[38;2;111;40;217m▀\x1b[48;2;9;217;69m\x1b[38;2;115;137;104m▀\x1b[m +\x1b[48;2;106;72;209m\x1b[38;2;151;183;253m▀\x1b[48;2;120;239;0m\x1b[38;2;25;2;162m▀\x1b[48;2;203;72;26m\x1b[38;2;206;77;28m▀\x1b[48;2;42;24;11m\x1b[38;2;42;25;11m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;13;8m\x1b[38;2;10;28;7m▀\x1b[48;2;9;36;6m\x1b[38;2;7;78;5m▀\x1b[48;2;2;153;1m\x1b[38;2;6;94;4m▀\x1b[48;2;0;178;0m\x1b[38;2;2;156;1m▀\x1b[48;2;0;175;0m\x1b[38;2;1;167;1m▀\x1b[48;2;0;177;0m\x1b[38;2;2;145;2m▀\x1b[48;2;2;147;2m\x1b[38;2;8;54;6m▀\x1b[48;2;9;38;6m\x1b[38;2;11;13;8m▀\x1b[48;2;11;13;8m\x1b[38;2;11;14;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;29;20;10m\x1b[38;2;29;21;10m▀\x1b[48;2;190;69;25m\x1b[38;2;193;74;27m▀\x1b[48;2;136;91;148m\x1b[38;2;42;159;86m▀\x1b[48;2;89;85;149m\x1b[38;2;160;5;219m▀\x1b[m +\x1b[48;2;229;106;143m\x1b[38;2;40;239;187m▀\x1b[48;2;196;134;237m\x1b[38;2;6;11;95m▀\x1b[48;2;197;60;22m\x1b[38;2;201;67;24m▀\x1b[48;2;41;22;10m\x1b[38;2;41;23;11m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[48;2;10;14;7m\x1b[38;2;11;15;8m▀▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;16;7m▀\x1b[48;2;11;15;7m\x1b[38;2;7;79;5m▀\x1b[48;2;7;68;5m\x1b[38;2;1;164;1m▀\x1b[48;2;2;153;1m\x1b[38;2;0;176;0m▀\x1b[48;2;2;154;1m\x1b[38;2;0;175;0m▀\x1b[48;2;5;107;3m\x1b[38;2;1;171;1m▀\x1b[48;2;4;115;3m\x1b[38;2;5;105;3m▀\x1b[48;2;6;84;4m\x1b[38;2;11;18;7m▀\x1b[48;2;10;30;7m\x1b[38;2;11;13;8m▀\x1b[48;2;11;13;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;14;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;29;19;9m\x1b[38;2;29;20;10m▀\x1b[48;2;185;58;22m\x1b[38;2;188;64;24m▀\x1b[48;2;68;241;49m\x1b[38;2;199;22;211m▀\x1b[48;2;133;139;8m\x1b[38;2;239;129;78m▀\x1b[m +\x1b[48;2;74;30;32m\x1b[38;2;163;185;76m▀\x1b[48;2;110;172;9m\x1b[38;2;177;1;123m▀\x1b[48;2;189;43;16m\x1b[38;2;193;52;19m▀\x1b[48;2;39;20;9m\x1b[38;2;40;21;10m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;9;14;7m\x1b[38;2;11;14;8m▀\x1b[48;2;106;54;38m\x1b[38;2;31;24;15m▀\x1b[48;2;164;71;49m\x1b[38;2;24;20;12m▀\x1b[48;2;94;46;31m\x1b[38;2;8;14;7m▀\x1b[48;2;36;24;15m\x1b[38;2;9;14;7m▀\x1b[48;2;11;15;8m\x1b[38;2;11;14;7m▀\x1b[48;2;8;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;10;14;7m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;13;8m▀\x1b[48;2;11;13;8m\x1b[38;2;9;45;6m▀\x1b[48;2;10;19;7m\x1b[38;2;7;75;5m▀\x1b[48;2;6;83;4m\x1b[38;2;2;143;2m▀\x1b[48;2;2;156;1m\x1b[38;2;0;176;0m▀\x1b[48;2;0;177;0m\x1b[38;2;0;175;0m▀\x1b[38;2;3;134;2m▀\x1b[48;2;2;152;1m\x1b[38;2;9;46;6m▀\x1b[48;2;8;60;5m\x1b[38;2;11;13;8m▀\x1b[48;2;11;14;7m\x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;28;18;9m \x1b[48;2;177;43;16m\x1b[38;2;181;51;19m▀\x1b[48;2;93;35;236m\x1b[38;2;224;10;142m▀\x1b[48;2;72;51;52m\x1b[38;2;213;112;158m▀\x1b[m +\x1b[48;2;175;209;155m\x1b[38;2;7;131;221m▀\x1b[48;2;24;0;85m\x1b[38;2;44;86;152m▀\x1b[48;2;181;27;10m\x1b[38;2;185;35;13m▀\x1b[48;2;38;17;8m\x1b[38;2;39;18;9m▀\x1b[48;2;9;14;7m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m \x1b[48;2;9;14;7m \x1b[48;2;87;43;32m\x1b[38;2;114;54;39m▀\x1b[48;2;188;71;54m\x1b[38;2;211;82;59m▀\x1b[48;2;203;73;55m\x1b[38;2;204;80;57m▀\x1b[48;2;205;73;55m\x1b[38;2;178;71;51m▀\x1b[48;2;204;74;55m\x1b[38;2;119;52;37m▀\x1b[48;2;188;69;52m\x1b[38;2;54;29;19m▀\x1b[48;2;141;55;41m\x1b[38;2;16;17;9m▀\x1b[48;2;75;35;24m\x1b[38;2;8;14;7m▀\x1b[48;2;26;20;12m\x1b[38;2;10;14;7m▀\x1b[48;2;9;14;7m\x1b[38;2;11;14;7m▀\x1b[38;2;11;15;8m▀\x1b[48;2;11;14;7m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m \x1b[48;2;11;13;8m\x1b[38;2;9;45;6m▀\x1b[48;2;10;23;7m\x1b[38;2;4;123;3m▀\x1b[48;2;7;75;5m\x1b[38;2;1;172;1m▀\x1b[48;2;6;84;4m\x1b[38;2;2;154;1m▀\x1b[48;2;4;114;3m\x1b[38;2;5;107;3m▀\x1b[48;2;5;103;4m\x1b[38;2;10;29;7m▀\x1b[48;2;10;23;7m\x1b[38;2;11;13;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;27;16;8m\x1b[38;2;27;17;9m▀\x1b[48;2;170;27;10m\x1b[38;2;174;35;13m▀\x1b[48;2;118;117;199m\x1b[38;2;249;61;74m▀\x1b[48;2;10;219;61m\x1b[38;2;187;245;202m▀\x1b[m +\x1b[48;2;20;155;44m\x1b[38;2;86;54;110m▀\x1b[48;2;195;85;113m\x1b[38;2;214;171;227m▀\x1b[48;2;173;10;4m\x1b[38;2;177;19;7m▀\x1b[48;2;37;14;7m\x1b[38;2;37;16;8m▀\x1b[48;2;9;15;8m\x1b[38;2;9;14;7m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;7m▀\x1b[48;2;11;14;7m\x1b[38;2;15;17;9m▀\x1b[48;2;9;14;7m\x1b[38;2;50;29;20m▀\x1b[48;2;10;15;8m\x1b[38;2;112;47;36m▀\x1b[48;2;33;22;15m\x1b[38;2;170;61;48m▀\x1b[48;2;88;38;29m\x1b[38;2;197;66;53m▀\x1b[48;2;151;53;43m\x1b[38;2;201;67;53m▀\x1b[48;2;189;60;50m▀\x1b[48;2;198;60;51m\x1b[38;2;194;65;52m▀\x1b[38;2;160;56;44m▀\x1b[48;2;196;60;50m\x1b[38;2;99;40;30m▀\x1b[48;2;174;55;47m\x1b[38;2;41;24;16m▀\x1b[48;2;122;43;35m\x1b[38;2;12;15;8m▀\x1b[48;2;59;27;20m\x1b[38;2;8;14;7m▀\x1b[48;2;16;16;9m\x1b[38;2;10;14;7m▀\x1b[48;2;10;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[38;2;11;14;8m▀\x1b[48;2;11;14;8m\x1b[38;2;11;12;8m▀\x1b[48;2;10;25;7m\x1b[38;2;7;79;5m▀\x1b[48;2;3;141;2m\x1b[38;2;1;174;1m▀\x1b[48;2;0;178;0m\x1b[38;2;1;169;1m▀\x1b[48;2;6;88;4m\x1b[38;2;8;56;6m▀\x1b[48;2;11;12;8m \x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;14;7m \x1b[48;2;26;15;8m\x1b[38;2;27;15;8m▀\x1b[48;2;162;12;5m\x1b[38;2;166;20;8m▀\x1b[48;2;143;168;130m\x1b[38;2;18;142;37m▀\x1b[48;2;240;96;105m\x1b[38;2;125;158;211m▀\x1b[m +\x1b[48;2;54;0;0m\x1b[38;2;187;22;0m▀\x1b[48;2;204;0;0m\x1b[38;2;128;208;0m▀\x1b[48;2;162;1;1m\x1b[38;2;168;3;1m▀\x1b[48;2;35;13;7m\x1b[38;2;36;13;7m▀\x1b[48;2;9;15;8m \x1b[48;2;11;15;8m \x1b[38;2;11;14;7m▀\x1b[38;2;9;14;7m▀\x1b[38;2;8;14;7m▀\x1b[48;2;10;14;7m\x1b[38;2;21;18;11m▀\x1b[48;2;7;13;6m\x1b[38;2;65;30;23m▀\x1b[48;2;12;16;9m\x1b[38;2;129;45;38m▀\x1b[48;2;57;29;23m\x1b[38;2;176;53;47m▀\x1b[48;2;148;49;44m\x1b[38;2;191;53;48m▀\x1b[48;2;187;52;48m\x1b[38;2;192;53;48m▀\x1b[48;2;186;51;47m\x1b[38;2;194;54;49m▀\x1b[48;2;182;52;47m\x1b[38;2;178;52;46m▀\x1b[48;2;59;27;21m\x1b[38;2;53;26;19m▀\x1b[48;2;8;14;7m \x1b[48;2;11;15;8m \x1b[48;2;11;14;8m\x1b[38;2;11;15;8m▀\x1b[48;2;11;12;8m\x1b[38;2;11;14;8m▀\x1b[48;2;10;30;7m\x1b[38;2;10;23;7m▀\x1b[48;2;5;110;3m\x1b[38;2;3;138;2m▀\x1b[48;2;2;149;2m\x1b[38;2;0;181;0m▀\x1b[48;2;6;92;4m\x1b[38;2;5;100;4m▀\x1b[48;2;11;13;8m \x1b[48;2;11;14;8m \x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;25;14;7m\x1b[38;2;26;14;7m▀\x1b[48;2;152;2;1m\x1b[38;2;158;5;2m▀\x1b[48;2;6;0;0m\x1b[38;2;44;193;0m▀\x1b[48;2;108;0;0m\x1b[38;2;64;70;0m▀\x1b[m +\x1b[48;2;44;0;0m\x1b[38;2;177;0;0m▀\x1b[48;2;147;0;0m\x1b[38;2;71;0;0m▀\x1b[48;2;148;1;1m\x1b[38;2;155;1;1m▀\x1b[48;2;33;13;7m\x1b[38;2;34;13;7m▀\x1b[48;2;9;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;10;14;7m▀\x1b[48;2;9;14;7m▀\x1b[48;2;13;16;9m\x1b[38;2;11;14;7m▀\x1b[48;2;42;24;17m\x1b[38;2;9;14;7m▀\x1b[48;2;97;38;32m\x1b[38;2;10;15;8m▀\x1b[48;2;149;49;44m\x1b[38;2;30;21;14m▀\x1b[48;2;174;52;48m\x1b[38;2;79;34;28m▀\x1b[48;2;178;52;48m\x1b[38;2;136;45;40m▀\x1b[38;2;172;51;47m▀\x1b[48;2;173;52;48m\x1b[38;2;181;52;48m▀\x1b[48;2;147;47;42m\x1b[38;2;183;52;48m▀\x1b[48;2;94;35;30m\x1b[38;2;177;52;48m▀\x1b[48;2;25;19;12m\x1b[38;2;56;27;20m▀\x1b[48;2;10;14;7m\x1b[38;2;8;14;7m▀\x1b[48;2;11;12;8m\x1b[38;2;11;15;8m▀\x1b[48;2;10;23;7m\x1b[38;2;11;14;8m▀\x1b[48;2;7;76;5m\x1b[38;2;11;13;8m▀\x1b[48;2;2;152;1m\x1b[38;2;9;45;6m▀\x1b[48;2;0;177;0m\x1b[38;2;5;106;3m▀\x1b[48;2;0;178;0m\x1b[38;2;4;123;3m▀\x1b[48;2;1;168;1m\x1b[38;2;5;104;3m▀\x1b[48;2;8;53;6m\x1b[38;2;9;47;6m▀\x1b[48;2;11;12;8m\x1b[38;2;11;13;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;24;14;7m\x1b[38;2;25;14;7m▀\x1b[48;2;140;2;1m\x1b[38;2;146;2;1m▀\x1b[48;2;219;0;0m\x1b[38;2;225;0;0m▀\x1b[48;2;126;0;0m\x1b[38;2;117;0;0m▀\x1b[m +\x1b[48;2;34;0;0m\x1b[38;2;167;0;0m▀\x1b[48;2;89;0;0m\x1b[38;2;14;0;0m▀\x1b[48;2;134;1;1m\x1b[38;2;141;1;1m▀\x1b[48;2;31;13;7m\x1b[38;2;32;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀\x1b[48;2;10;14;7m\x1b[38;2;11;14;7m▀\x1b[48;2;53;29;22m\x1b[38;2;10;14;7m▀\x1b[48;2;127;46;41m\x1b[38;2;20;18;11m▀\x1b[48;2;158;51;47m\x1b[38;2;57;28;22m▀\x1b[48;2;166;52;48m\x1b[38;2;113;42;36m▀\x1b[48;2;167;52;48m\x1b[38;2;156;50;46m▀\x1b[48;2;164;52;48m\x1b[38;2;171;52;48m▀\x1b[48;2;146;48;44m\x1b[38;2;172;52;48m▀\x1b[48;2;102;38;33m▀\x1b[48;2;50;26;19m\x1b[38;2;161;51;46m▀\x1b[48;2;17;17;10m\x1b[38;2;126;44;38m▀\x1b[48;2;8;14;7m\x1b[38;2;71;31;25m▀\x1b[48;2;10;14;7m\x1b[38;2;27;19;13m▀\x1b[48;2;11;13;8m\x1b[38;2;10;14;7m▀\x1b[48;2;9;40;6m\x1b[38;2;10;13;7m▀\x1b[48;2;4;119;3m\x1b[38;2;11;20;7m▀\x1b[48;2;1;168;1m\x1b[38;2;8;63;5m▀\x1b[48;2;0;177;0m\x1b[38;2;3;130;2m▀\x1b[48;2;0;175;0m\x1b[38;2;1;171;1m▀\x1b[48;2;1;174;1m\x1b[38;2;0;176;0m▀\x1b[48;2;1;175;1m\x1b[38;2;1;174;1m▀\x1b[48;2;0;177;0m\x1b[38;2;0;176;0m▀\x1b[48;2;3;134;2m\x1b[38;2;2;158;1m▀\x1b[48;2;10;21;7m\x1b[38;2;9;38;6m▀\x1b[48;2;11;14;8m\x1b[38;2;11;13;8m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;23;14;7m \x1b[48;2;127;2;1m\x1b[38;2;133;2;1m▀\x1b[48;2;176;0;0m\x1b[38;2;213;0;0m▀\x1b[48;2;109;0;0m\x1b[38;2;100;0;0m▀\x1b[m +\x1b[48;2;24;0;0m\x1b[38;2;157;0;0m▀\x1b[48;2;32;0;0m\x1b[38;2;165;0;0m▀\x1b[48;2;121;1;1m\x1b[38;2;128;1;1m▀\x1b[48;2;28;13;7m\x1b[38;2;30;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m \x1b[48;2;9;15;7m \x1b[48;2;88;41;34m\x1b[38;2;91;41;34m▀\x1b[48;2;145;51;47m\x1b[38;2;163;53;49m▀\x1b[48;2;107;42;36m\x1b[38;2;161;52;48m▀\x1b[48;2;58;29;22m\x1b[38;2;155;51;47m▀\x1b[48;2;21;18;11m\x1b[38;2;128;45;40m▀\x1b[48;2;9;14;7m\x1b[38;2;79;33;27m▀\x1b[38;2;33;21;15m▀\x1b[48;2;11;14;7m\x1b[38;2;12;15;8m▀\x1b[48;2;11;15;8m\x1b[38;2;9;14;7m▀\x1b[38;2;10;14;7m▀ \x1b[48;2;11;12;8m\x1b[38;2;11;14;8m▀\x1b[48;2;8;54;6m\x1b[38;2;10;28;7m▀\x1b[48;2;6;93;4m\x1b[38;2;4;125;3m▀\x1b[48;2;2;152;1m\x1b[38;2;0;175;0m▀\x1b[48;2;0;176;0m▀\x1b[48;2;0;175;0m\x1b[38;2;1;174;1m▀\x1b[48;2;0;177;0m\x1b[38;2;1;175;1m▀\x1b[48;2;0;175;0m▀▀\x1b[48;2;1;162;1m\x1b[38;2;0;176;0m▀\x1b[48;2;9;47;6m\x1b[38;2;6;95;4m▀\x1b[48;2;11;13;8m \x1b[48;2;11;15;8m\x1b[38;2;11;14;8m▀ \x1b[48;2;10;15;8m \x1b[48;2;21;13;7m\x1b[38;2;22;13;7m▀\x1b[48;2;114;2;1m\x1b[38;2;121;2;1m▀\x1b[48;2;164;0;0m\x1b[38;2;170;0;0m▀\x1b[48;2;127;0;0m\x1b[38;2;118;0;0m▀\x1b[m +\x1b[48;2;14;0;0m\x1b[38;2;147;0;0m▀\x1b[48;2;183;0;0m\x1b[38;2;108;0;0m▀\x1b[48;2;107;1;1m\x1b[38;2;114;1;1m▀\x1b[48;2;26;13;7m\x1b[38;2;27;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[38;2;11;14;7m▀ \x1b[48;2;10;14;7m\x1b[38;2;43;27;20m▀\x1b[48;2;9;14;7m\x1b[38;2;42;25;18m▀\x1b[48;2;11;14;7m\x1b[38;2;14;16;9m▀\x1b[48;2;11;15;8m\x1b[38;2;9;14;7m▀\x1b[38;2;10;14;7m▀\x1b[38;2;11;14;7m▀ \x1b[48;2;11;12;8m \x1b[48;2;9;49;6m\x1b[38;2;8;64;5m▀\x1b[48;2;1;166;1m\x1b[38;2;1;159;1m▀\x1b[48;2;0;175;0m\x1b[38;2;1;171;1m▀ \x1b[48;2;1;159;1m\x1b[38;2;1;167;1m▀\x1b[48;2;7;79;5m\x1b[38;2;4;122;3m▀\x1b[48;2;2;144;2m\x1b[38;2;2;158;1m▀\x1b[48;2;0;158;1m\x1b[38;2;0;177;0m▀\x1b[48;2;7;44;6m\x1b[38;2;4;112;3m▀\x1b[48;2;9;12;7m\x1b[38;2;11;17;7m▀\x1b[48;2;9;14;7m\x1b[38;2;11;14;8m▀\x1b[38;2;11;15;8m▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;11;14;7m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;20;13;7m\x1b[38;2;21;13;7m▀\x1b[48;2;102;2;1m\x1b[38;2;108;2;1m▀\x1b[48;2;121;0;0m\x1b[38;2;127;0;0m▀\x1b[48;2;146;0;0m\x1b[38;2;136;0;0m▀\x1b[m +\x1b[48;2;3;0;0m\x1b[38;2;137;0;0m▀\x1b[48;2;173;0;0m\x1b[38;2;50;0;0m▀\x1b[48;2;93;1;1m\x1b[38;2;100;1;1m▀\x1b[48;2;24;13;7m\x1b[38;2;25;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;11;15;8m▀▀\x1b[48;2;17;14;7m\x1b[38;2;11;14;8m▀\x1b[48;2;49;12;7m\x1b[38;2;9;24;7m▀\x1b[48;2;62;54;4m\x1b[38;2;8;133;2m▀\x1b[48;2;7;159;1m\x1b[38;2;2;176;0m▀\x1b[48;2;0;175;0m \x1b[48;2;1;172;1m\x1b[38;2;0;175;0m▀\x1b[48;2;1;159;1m\x1b[38;2;0;173;1m▀\x1b[48;2;46;122;19m\x1b[38;2;1;176;0m▀\x1b[48;2;122;63;45m\x1b[38;2;45;111;18m▀\x1b[48;2;135;52;49m\x1b[38;2;75;36;31m▀\x1b[48;2;135;53;49m\x1b[38;2;74;36;30m▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;136;53;49m\x1b[38;2;75;37;31m▀\x1b[48;2;119;49;45m\x1b[38;2;66;34;28m▀\x1b[48;2;25;20;13m\x1b[38;2;18;18;11m▀\x1b[48;2;10;14;7m \x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;19;13;7m \x1b[48;2;89;2;1m\x1b[38;2;95;2;1m▀\x1b[48;2;77;0;0m\x1b[38;2;83;0;0m▀\x1b[48;2;128;0;0m\x1b[38;2;119;0;0m▀\x1b[m +\x1b[48;2;60;0;0m\x1b[38;2;126;0;0m▀\x1b[48;2;182;0;0m\x1b[38;2;249;0;0m▀\x1b[48;2;83;1;1m\x1b[38;2;87;1;1m▀\x1b[48;2;22;13;7m\x1b[38;2;23;13;7m▀\x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;11;14;7m\x1b[38;2;16;14;7m▀\x1b[48;2;14;14;7m\x1b[38;2;42;13;7m▀\x1b[48;2;58;13;6m\x1b[38;2;95;11;5m▀\x1b[48;2;34;13;7m\x1b[38;2;100;11;5m▀\x1b[48;2;9;14;7m\x1b[38;2;21;17;7m▀\x1b[48;2;11;12;8m\x1b[38;2;8;55;6m▀\x1b[38;2;7;75;5m▀\x1b[38;2;8;65;5m▀\x1b[48;2;11;13;8m\x1b[38;2;9;41;6m▀\x1b[48;2;12;15;8m\x1b[38;2;60;37;28m▀\x1b[38;2;90;42;37m▀\x1b[38;2;88;42;36m▀▀▀▀▀▀▀▀▀▀▀▀\x1b[38;2;89;42;37m▀\x1b[38;2;78;39;33m▀\x1b[48;2;11;15;8m\x1b[38;2;20;18;11m▀\x1b[48;2;11;14;7m\x1b[38;2;10;14;7m▀\x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;18;13;7m \x1b[48;2;78;2;1m\x1b[38;2;83;2;1m▀\x1b[48;2;196;0;0m\x1b[38;2;40;0;0m▀\x1b[48;2;217;0;0m\x1b[38;2;137;0;0m▀\x1b[m +\x1b[48;2;227;0;0m\x1b[38;2;16;0;0m▀\x1b[48;2;116;0;0m\x1b[38;2;21;0;0m▀\x1b[48;2;79;1;1m\x1b[38;2;81;1;1m▀\x1b[48;2;22;13;7m \x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[38;2;10;15;8m▀\x1b[48;2;10;15;8m\x1b[38;2;21;14;7m▀\x1b[48;2;11;15;8m\x1b[38;2;14;14;7m▀\x1b[38;2;11;14;7m▀ ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ \x1b[48;2;10;15;8m \x1b[48;2;17;13;7m\x1b[38;2;18;13;7m▀\x1b[48;2;75;2;1m\x1b[38;2;76;2;1m▀\x1b[48;2;97;0;0m\x1b[38;2;34;0;0m▀\x1b[48;2;76;0;0m\x1b[38;2;147;0;0m▀\x1b[m +\x1b[48;2;161;0;0m\x1b[38;2;183;0;0m▀\x1b[48;2;49;0;0m\x1b[38;2;211;0;0m▀\x1b[48;2;75;1;1m\x1b[38;2;77;1;1m▀\x1b[48;2;21;13;7m \x1b[48;2;10;15;8m \x1b[48;2;11;15;8m \x1b[48;2;10;15;8m \x1b[48;2;17;13;7m \x1b[48;2;71;2;1m\x1b[38;2;73;2;1m▀\x1b[48;2;253;0;0m\x1b[38;2;159;0;0m▀\x1b[48;2;191;0;0m\x1b[38;2;5;0;0m▀\x1b[m +\x1b[48;2;110;161;100m\x1b[38;2;116;0;0m▀\x1b[48;2;9;205;205m\x1b[38;2;192;0;0m▀\x1b[48;2;78;0;0m\x1b[38;2;77;1;0m▀\x1b[48;2;66;3;1m\x1b[38;2;30;11;6m▀\x1b[48;2;42;8;4m\x1b[38;2;9;15;8m▀\x1b[48;2;39;8;4m\x1b[38;2;10;15;8m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;40;8;4m▀\x1b[48;2;39;8;4m▀▀▀▀▀▀▀\x1b[48;2;40;8;4m▀▀▀\x1b[48;2;39;8;4m▀\x1b[48;2;40;8;4m▀\x1b[48;2;39;8;4m▀\x1b[48;2;41;8;4m\x1b[38;2;9;15;8m▀\x1b[48;2;62;4;2m\x1b[38;2;24;13;7m▀\x1b[48;2;78;0;0m\x1b[38;2;74;1;1m▀\x1b[48;2;221;222;0m\x1b[38;2;59;0;0m▀\x1b[48;2;67;199;133m\x1b[38;2;85;0;0m▀\x1b[m +\x1b[48;2;0;0;0m\x1b[38;2;143;233;149m▀\x1b[48;2;108;184;254m\x1b[38;2;213;6;76m▀\x1b[48;2;197;183;82m\x1b[38;2;76;0;0m▀\x1b[48;2;154;157;0m▀\x1b[48;2;96;0;0m▀\x1b[48;2;253;0;0m▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀\x1b[48;2;226;0;0m▀\x1b[48;2;255;127;255m▀\x1b[48;2;84;36;66m\x1b[38;2;64;247;251m▀\x1b[48;2;0;0;0m\x1b[38;2;18;76;210m▀\x1b[m +\x1b[48;2;0;0;0m \x1b[m +\x1b[48;2;0;0;0m \x1b[m +""" + ) +) diff --git a/examples/print-text/pygments-tokens.py b/examples/print-text/pygments-tokens.py new file mode 100755 index 0000000..3470e8e --- /dev/null +++ b/examples/print-text/pygments-tokens.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +Printing a list of Pygments (Token, text) tuples, +or an output of a Pygments lexer. +""" +import pygments +from pygments.lexers.python import PythonLexer +from pygments.token import Token + +from prompt_toolkit import print_formatted_text +from prompt_toolkit.formatted_text import PygmentsTokens +from prompt_toolkit.styles import Style + + +def main(): + # Printing a manually constructed list of (Token, text) tuples. + text = [ + (Token.Keyword, "print"), + (Token.Punctuation, "("), + (Token.Literal.String.Double, '"'), + (Token.Literal.String.Double, "hello"), + (Token.Literal.String.Double, '"'), + (Token.Punctuation, ")"), + (Token.Text, "\n"), + ] + + print_formatted_text(PygmentsTokens(text)) + + # Printing the output of a pygments lexer. + tokens = list(pygments.lex('print("Hello")', lexer=PythonLexer())) + print_formatted_text(PygmentsTokens(tokens)) + + # With a custom style. + style = Style.from_dict( + { + "pygments.keyword": "underline", + "pygments.literal.string": "bg:#00ff00 #ffffff", + } + ) + print_formatted_text(PygmentsTokens(tokens), style=style) + + +if __name__ == "__main__": + main() diff --git a/examples/print-text/true-color-demo.py b/examples/print-text/true-color-demo.py new file mode 100755 index 0000000..a241006 --- /dev/null +++ b/examples/print-text/true-color-demo.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +""" +Demonstration of all the ANSI colors. +""" +from prompt_toolkit import print_formatted_text +from prompt_toolkit.formatted_text import HTML, FormattedText +from prompt_toolkit.output import ColorDepth + +print = print_formatted_text + + +def main(): + print(HTML("\nTrue color test.")) + + for template in [ + "bg:#{0:02x}0000", # Red. + "bg:#00{0:02x}00", # Green. + "bg:#0000{0:02x}", # Blue. + "bg:#{0:02x}{0:02x}00", # Yellow. + "bg:#{0:02x}00{0:02x}", # Magenta. + "bg:#00{0:02x}{0:02x}", # Cyan. + "bg:#{0:02x}{0:02x}{0:02x}", # Gray. + ]: + fragments = [] + for i in range(0, 256, 4): + fragments.append((template.format(i), " ")) + + print(FormattedText(fragments), color_depth=ColorDepth.DEPTH_4_BIT) + print(FormattedText(fragments), color_depth=ColorDepth.DEPTH_8_BIT) + print(FormattedText(fragments), color_depth=ColorDepth.DEPTH_24_BIT) + print() + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/a-lot-of-parallel-tasks.py b/examples/progress-bar/a-lot-of-parallel-tasks.py new file mode 100755 index 0000000..31110ac --- /dev/null +++ b/examples/progress-bar/a-lot-of-parallel-tasks.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +""" +More complex demonstration of what's possible with the progress bar. +""" +import random +import threading +import time + +from prompt_toolkit import HTML +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + with ProgressBar( + title=HTML("Example of many parallel tasks."), + bottom_toolbar=HTML("[Control-L] clear [Control-C] abort"), + ) as pb: + + def run_task(label, total, sleep_time): + """Complete a normal run.""" + for i in pb(range(total), label=label): + time.sleep(sleep_time) + + def stop_task(label, total, sleep_time): + """Stop at some random index. + + Breaking out of iteration at some stop index mimics how progress + bars behave in cases where errors are raised. + """ + stop_i = random.randrange(total) + bar = pb(range(total), label=label) + for i in bar: + if stop_i == i: + bar.label = f"{label} BREAK" + break + time.sleep(sleep_time) + + threads = [] + + for i in range(160): + label = "Task %i" % i + total = random.randrange(50, 200) + sleep_time = random.randrange(5, 20) / 100.0 + + threads.append( + threading.Thread( + target=random.choice((run_task, stop_task)), + args=(label, total, sleep_time), + ) + ) + + for t in threads: + t.daemon = True + t.start() + + # Wait for the threads to finish. We use a timeout for the join() call, + # because on Windows, join cannot be interrupted by Control-C or any other + # signal. + for t in threads: + while t.is_alive(): + t.join(timeout=0.5) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/colored-title-and-label.py b/examples/progress-bar/colored-title-and-label.py new file mode 100755 index 0000000..0b5e73a --- /dev/null +++ b/examples/progress-bar/colored-title-and-label.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +""" +A progress bar that displays a formatted title above the progress bar and has a +colored label. +""" +import time + +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + title = HTML('Downloading ') + label = HTML("some file: ") + + with ProgressBar(title=title) as pb: + for i in pb(range(800), label=label): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/custom-key-bindings.py b/examples/progress-bar/custom-key-bindings.py new file mode 100755 index 0000000..f700811 --- /dev/null +++ b/examples/progress-bar/custom-key-bindings.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python +""" +A very simple progress bar which keep track of the progress as we consume an +iterator. +""" +import os +import signal +import time + +from prompt_toolkit import HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.patch_stdout import patch_stdout +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + bottom_toolbar = HTML( + ' [f] Print "f" [q] Abort [x] Send Control-C.' + ) + + # Create custom key bindings first. + kb = KeyBindings() + cancel = [False] + + @kb.add("f") + def _(event): + print("You pressed `f`.") + + @kb.add("q") + def _(event): + "Quit by setting cancel flag." + cancel[0] = True + + @kb.add("x") + def _(event): + "Quit by sending SIGINT to the main thread." + os.kill(os.getpid(), signal.SIGINT) + + # Use `patch_stdout`, to make sure that prints go above the + # application. + with patch_stdout(): + with ProgressBar(key_bindings=kb, bottom_toolbar=bottom_toolbar) as pb: + for i in pb(range(800)): + time.sleep(0.01) + + if cancel[0]: + break + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/many-parallel-tasks.py b/examples/progress-bar/many-parallel-tasks.py new file mode 100755 index 0000000..dc34ef2 --- /dev/null +++ b/examples/progress-bar/many-parallel-tasks.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python +""" +More complex demonstration of what's possible with the progress bar. +""" +import threading +import time + +from prompt_toolkit import HTML +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + with ProgressBar( + title=HTML("Example of many parallel tasks."), + bottom_toolbar=HTML("[Control-L] clear [Control-C] abort"), + ) as pb: + + def run_task(label, total, sleep_time): + for i in pb(range(total), label=label): + time.sleep(sleep_time) + + threads = [ + threading.Thread(target=run_task, args=("First task", 50, 0.1)), + threading.Thread(target=run_task, args=("Second task", 100, 0.1)), + threading.Thread(target=run_task, args=("Third task", 8, 3)), + threading.Thread(target=run_task, args=("Fourth task", 200, 0.1)), + threading.Thread(target=run_task, args=("Fifth task", 40, 0.2)), + threading.Thread(target=run_task, args=("Sixth task", 220, 0.1)), + threading.Thread(target=run_task, args=("Seventh task", 85, 0.05)), + threading.Thread(target=run_task, args=("Eight task", 200, 0.05)), + ] + + for t in threads: + t.daemon = True + t.start() + + # Wait for the threads to finish. We use a timeout for the join() call, + # because on Windows, join cannot be interrupted by Control-C or any other + # signal. + for t in threads: + while t.is_alive(): + t.join(timeout=0.5) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/nested-progress-bars.py b/examples/progress-bar/nested-progress-bars.py new file mode 100755 index 0000000..1a1e706 --- /dev/null +++ b/examples/progress-bar/nested-progress-bars.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +""" +Example of nested progress bars. +""" +import time + +from prompt_toolkit import HTML +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + with ProgressBar( + title=HTML('Nested progress bars'), + bottom_toolbar=HTML(" [Control-L] clear [Control-C] abort"), + ) as pb: + for i in pb(range(6), label="Main task"): + for j in pb(range(200), label=f"Subtask <{i + 1}>", remove_when_done=True): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/scrolling-task-name.py b/examples/progress-bar/scrolling-task-name.py new file mode 100755 index 0000000..bce155f --- /dev/null +++ b/examples/progress-bar/scrolling-task-name.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python +""" +A very simple progress bar where the name of the task scrolls, because it's too long. +iterator. +""" +import time + +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + with ProgressBar( + title="Scrolling task name (make sure the window is not too big)." + ) as pb: + for i in pb( + range(800), + label="This is a very very very long task that requires horizontal scrolling ...", + ): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/simple-progress-bar.py b/examples/progress-bar/simple-progress-bar.py new file mode 100755 index 0000000..c8776e5 --- /dev/null +++ b/examples/progress-bar/simple-progress-bar.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +""" +A very simple progress bar which keep track of the progress as we consume an +iterator. +""" +import time + +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + with ProgressBar() as pb: + for i in pb(range(800)): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/styled-1.py b/examples/progress-bar/styled-1.py new file mode 100755 index 0000000..d972e55 --- /dev/null +++ b/examples/progress-bar/styled-1.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +A very simple progress bar which keep track of the progress as we consume an +iterator. +""" +import time + +from prompt_toolkit.shortcuts import ProgressBar +from prompt_toolkit.styles import Style + +style = Style.from_dict( + { + "title": "#4444ff underline", + "label": "#ff4400 bold", + "percentage": "#00ff00", + "bar-a": "bg:#00ff00 #004400", + "bar-b": "bg:#00ff00 #000000", + "bar-c": "#000000 underline", + "current": "#448844", + "total": "#448844", + "time-elapsed": "#444488", + "time-left": "bg:#88ff88 #000000", + } +) + + +def main(): + with ProgressBar( + style=style, title="Progress bar example with custom styling." + ) as pb: + for i in pb(range(1600), label="Downloading..."): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/styled-2.py b/examples/progress-bar/styled-2.py new file mode 100755 index 0000000..15c57d4 --- /dev/null +++ b/examples/progress-bar/styled-2.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python +""" +A very simple progress bar which keep track of the progress as we consume an +iterator. +""" +import time + +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import ProgressBar +from prompt_toolkit.shortcuts.progress_bar import formatters +from prompt_toolkit.styles import Style + +style = Style.from_dict( + { + "progressbar title": "#0000ff", + "item-title": "#ff4400 underline", + "percentage": "#00ff00", + "bar-a": "bg:#00ff00 #004400", + "bar-b": "bg:#00ff00 #000000", + "bar-c": "bg:#000000 #000000", + "tildes": "#444488", + "time-left": "bg:#88ff88 #ffffff", + "spinning-wheel": "bg:#ffff00 #000000", + } +) + + +def main(): + custom_formatters = [ + formatters.Label(), + formatters.Text(" "), + formatters.SpinningWheel(), + formatters.Text(" "), + formatters.Text(HTML("~~~")), + formatters.Bar(sym_a="#", sym_b="#", sym_c="."), + formatters.Text(" left: "), + formatters.TimeLeft(), + ] + with ProgressBar( + title="Progress bar example with custom formatter.", + formatters=custom_formatters, + style=style, + ) as pb: + for i in pb(range(20), label="Downloading..."): + time.sleep(1) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/styled-apt-get-install.py b/examples/progress-bar/styled-apt-get-install.py new file mode 100755 index 0000000..bafe70b --- /dev/null +++ b/examples/progress-bar/styled-apt-get-install.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +""" +Styled just like an apt-get installation. +""" +import time + +from prompt_toolkit.shortcuts import ProgressBar +from prompt_toolkit.shortcuts.progress_bar import formatters +from prompt_toolkit.styles import Style + +style = Style.from_dict( + { + "label": "bg:#ffff00 #000000", + "percentage": "bg:#ffff00 #000000", + "current": "#448844", + "bar": "", + } +) + + +def main(): + custom_formatters = [ + formatters.Label(), + formatters.Text(": [", style="class:percentage"), + formatters.Percentage(), + formatters.Text("]", style="class:percentage"), + formatters.Text(" "), + formatters.Bar(sym_a="#", sym_b="#", sym_c="."), + formatters.Text(" "), + ] + + with ProgressBar(style=style, formatters=custom_formatters) as pb: + for i in pb(range(1600), label="Installing"): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/styled-rainbow.py b/examples/progress-bar/styled-rainbow.py new file mode 100755 index 0000000..b46e949 --- /dev/null +++ b/examples/progress-bar/styled-rainbow.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +""" +A simple progress bar, visualized with rainbow colors (for fun). +""" +import time + +from prompt_toolkit.output import ColorDepth +from prompt_toolkit.shortcuts import ProgressBar +from prompt_toolkit.shortcuts.progress_bar import formatters +from prompt_toolkit.shortcuts.prompt import confirm + + +def main(): + true_color = confirm("Yes true colors? (y/n) ") + + custom_formatters = [ + formatters.Label(), + formatters.Text(" "), + formatters.Rainbow(formatters.Bar()), + formatters.Text(" left: "), + formatters.Rainbow(formatters.TimeLeft()), + ] + + if true_color: + color_depth = ColorDepth.DEPTH_24_BIT + else: + color_depth = ColorDepth.DEPTH_8_BIT + + with ProgressBar(formatters=custom_formatters, color_depth=color_depth) as pb: + for i in pb(range(20), label="Downloading..."): + time.sleep(1) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/styled-tqdm-1.py b/examples/progress-bar/styled-tqdm-1.py new file mode 100755 index 0000000..9484ac0 --- /dev/null +++ b/examples/progress-bar/styled-tqdm-1.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +""" +Styled similar to tqdm, another progress bar implementation in Python. + +See: https://github.com/noamraph/tqdm +""" +import time + +from prompt_toolkit.shortcuts import ProgressBar +from prompt_toolkit.shortcuts.progress_bar import formatters +from prompt_toolkit.styles import Style + +style = Style.from_dict({"": "cyan"}) + + +def main(): + custom_formatters = [ + formatters.Label(suffix=": "), + formatters.Bar(start="|", end="|", sym_a="#", sym_b="#", sym_c="-"), + formatters.Text(" "), + formatters.Progress(), + formatters.Text(" "), + formatters.Percentage(), + formatters.Text(" [elapsed: "), + formatters.TimeElapsed(), + formatters.Text(" left: "), + formatters.TimeLeft(), + formatters.Text(", "), + formatters.IterationsPerSecond(), + formatters.Text(" iters/sec]"), + formatters.Text(" "), + ] + + with ProgressBar(style=style, formatters=custom_formatters) as pb: + for i in pb(range(1600), label="Installing"): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/styled-tqdm-2.py b/examples/progress-bar/styled-tqdm-2.py new file mode 100755 index 0000000..0e66e90 --- /dev/null +++ b/examples/progress-bar/styled-tqdm-2.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python +""" +Styled similar to tqdm, another progress bar implementation in Python. + +See: https://github.com/noamraph/tqdm +""" +import time + +from prompt_toolkit.shortcuts import ProgressBar +from prompt_toolkit.shortcuts.progress_bar import formatters +from prompt_toolkit.styles import Style + +style = Style.from_dict({"bar-a": "reverse"}) + + +def main(): + custom_formatters = [ + formatters.Label(suffix=": "), + formatters.Percentage(), + formatters.Bar(start="|", end="|", sym_a=" ", sym_b=" ", sym_c=" "), + formatters.Text(" "), + formatters.Progress(), + formatters.Text(" ["), + formatters.TimeElapsed(), + formatters.Text("<"), + formatters.TimeLeft(), + formatters.Text(", "), + formatters.IterationsPerSecond(), + formatters.Text("it/s]"), + ] + + with ProgressBar(style=style, formatters=custom_formatters) as pb: + for i in pb(range(1600), label="Installing"): + time.sleep(0.01) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/two-tasks.py b/examples/progress-bar/two-tasks.py new file mode 100755 index 0000000..c78604e --- /dev/null +++ b/examples/progress-bar/two-tasks.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +""" +Two progress bars that run in parallel. +""" +import threading +import time + +from prompt_toolkit.shortcuts import ProgressBar + + +def main(): + with ProgressBar() as pb: + # Two parallal tasks. + def task_1(): + for i in pb(range(100)): + time.sleep(0.05) + + def task_2(): + for i in pb(range(150)): + time.sleep(0.08) + + # Start threads. + t1 = threading.Thread(target=task_1) + t2 = threading.Thread(target=task_2) + t1.daemon = True + t2.daemon = True + t1.start() + t2.start() + + # Wait for the threads to finish. We use a timeout for the join() call, + # because on Windows, join cannot be interrupted by Control-C or any other + # signal. + for t in [t1, t2]: + while t.is_alive(): + t.join(timeout=0.5) + + +if __name__ == "__main__": + main() diff --git a/examples/progress-bar/unknown-length.py b/examples/progress-bar/unknown-length.py new file mode 100755 index 0000000..e39ac39 --- /dev/null +++ b/examples/progress-bar/unknown-length.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +""" +A very simple progress bar which keep track of the progress as we consume an +iterator. +""" +import time + +from prompt_toolkit.shortcuts import ProgressBar + + +def data(): + """ + A generator that produces items. len() doesn't work here, so the progress + bar can't estimate the time it will take. + """ + yield from range(1000) + + +def main(): + with ProgressBar() as pb: + for i in pb(data()): + time.sleep(0.1) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/accept-default.py b/examples/prompts/accept-default.py new file mode 100644 index 0000000..311ef46 --- /dev/null +++ b/examples/prompts/accept-default.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python +""" +Example of `accept_default`, a way to automatically accept the input that the +user typed without allowing him/her to edit it. + +This should display the prompt with all the formatting like usual, but not +allow any editing. +""" +from prompt_toolkit import HTML, prompt + +if __name__ == "__main__": + answer = prompt( + HTML("Type some input: "), accept_default=True, default="test" + ) + + print("You said: %s" % answer) diff --git a/examples/prompts/asyncio-prompt.py b/examples/prompts/asyncio-prompt.py new file mode 100755 index 0000000..32a1481 --- /dev/null +++ b/examples/prompts/asyncio-prompt.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +""" +This is an example of how to prompt inside an application that uses the asyncio +eventloop. The ``prompt_toolkit`` library will make sure that when other +coroutines are writing to stdout, they write above the prompt, not destroying +the input line. +This example does several things: + 1. It starts a simple coroutine, printing a counter to stdout every second. + 2. It starts a simple input/echo app loop which reads from stdin. +Very important is the following patch. If you are passing stdin by reference to +other parts of the code, make sure that this patch is applied as early as +possible. :: + sys.stdout = app.stdout_proxy() +""" + +import asyncio + +from prompt_toolkit.patch_stdout import patch_stdout +from prompt_toolkit.shortcuts import PromptSession + + +async def print_counter(): + """ + Coroutine that prints counters. + """ + try: + i = 0 + while True: + print("Counter: %i" % i) + i += 1 + await asyncio.sleep(3) + except asyncio.CancelledError: + print("Background task cancelled.") + + +async def interactive_shell(): + """ + Like `interactive_shell`, but doing things manual. + """ + # Create Prompt. + session = PromptSession("Say something: ") + + # Run echo loop. Read text from stdin, and reply it back. + while True: + try: + result = await session.prompt_async() + print(f'You said: "{result}"') + except (EOFError, KeyboardInterrupt): + return + + +async def main(): + with patch_stdout(): + background_task = asyncio.create_task(print_counter()) + try: + await interactive_shell() + finally: + background_task.cancel() + print("Quitting event loop. Bye.") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/prompts/auto-completion/autocomplete-with-control-space.py b/examples/prompts/auto-completion/autocomplete-with-control-space.py new file mode 100755 index 0000000..61160a3 --- /dev/null +++ b/examples/prompts/auto-completion/autocomplete-with-control-space.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python +""" +Example of using the control-space key binding for auto completion. +""" +from prompt_toolkit import prompt +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.key_binding import KeyBindings + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + + +kb = KeyBindings() + + +@kb.add("c-space") +def _(event): + """ + Start auto completion. If the menu is showing already, select the next + completion. + """ + b = event.app.current_buffer + if b.complete_state: + b.complete_next() + else: + b.start_completion(select_first=False) + + +def main(): + text = prompt( + "Give some animals: ", + completer=animal_completer, + complete_while_typing=False, + key_bindings=kb, + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/autocompletion-like-readline.py b/examples/prompts/auto-completion/autocompletion-like-readline.py new file mode 100755 index 0000000..613d3e7 --- /dev/null +++ b/examples/prompts/auto-completion/autocompletion-like-readline.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python +""" +Autocompletion example that displays the autocompletions like readline does by +binding a custom handler to the Tab key. +""" +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.shortcuts import CompleteStyle, prompt + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + + +def main(): + text = prompt( + "Give some animals: ", + completer=animal_completer, + complete_style=CompleteStyle.READLINE_LIKE, + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/autocompletion.py b/examples/prompts/auto-completion/autocompletion.py new file mode 100755 index 0000000..fc9dda0 --- /dev/null +++ b/examples/prompts/auto-completion/autocompletion.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +""" +Autocompletion example. + +Press [Tab] to complete the current word. +- The first Tab press fills in the common part of all completions + and shows all the completions. (In the menu) +- Any following tab press cycles through all the possible completions. +""" +from prompt_toolkit import prompt +from prompt_toolkit.completion import WordCompleter + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + + +def main(): + text = prompt( + "Give some animals: ", completer=animal_completer, complete_while_typing=False + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/colored-completions-with-formatted-text.py b/examples/prompts/auto-completion/colored-completions-with-formatted-text.py new file mode 100755 index 0000000..8a89c7a --- /dev/null +++ b/examples/prompts/auto-completion/colored-completions-with-formatted-text.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python +""" +Demonstration of a custom completer class and the possibility of styling +completions independently by passing formatted text objects to the "display" +and "display_meta" arguments of "Completion". +""" +from prompt_toolkit.completion import Completer, Completion +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import CompleteStyle, prompt + +animals = [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", +] + +animal_family = { + "alligator": "reptile", + "ant": "insect", + "ape": "mammal", + "bat": "mammal", + "bear": "mammal", + "beaver": "mammal", + "bee": "insect", + "bison": "mammal", + "butterfly": "insect", + "cat": "mammal", + "chicken": "bird", + "crocodile": "reptile", + "dinosaur": "reptile", + "dog": "mammal", + "dolphin": "mammal", + "dove": "bird", + "duck": "bird", + "eagle": "bird", + "elephant": "mammal", +} + +family_colors = { + "mammal": "ansimagenta", + "insect": "ansigreen", + "reptile": "ansired", + "bird": "ansiyellow", +} + +meta = { + "alligator": HTML( + "An alligator is a crocodilian in the genus Alligator of the family Alligatoridae." + ), + "ant": HTML( + "Ants are eusocial insects of the family Formicidae." + ), + "ape": HTML( + "Apes (Hominoidea) are a branch of Old World tailless anthropoid catarrhine primates." + ), + "bat": HTML("Bats are mammals of the order Chiroptera."), + "bee": HTML( + "Bees are flying insects closely related to wasps and ants." + ), + "beaver": HTML( + "The beaver (genus Castor) is a large, primarily nocturnal, semiaquatic rodent." + ), + "bear": HTML( + "Bears are carnivoran mammals of the family Ursidae." + ), + "butterfly": HTML( + "Butterflies are insects in the macrolepidopteran clade Rhopalocera from the order Lepidoptera." + ), + # ... +} + + +class AnimalCompleter(Completer): + def get_completions(self, document, complete_event): + word = document.get_word_before_cursor() + for animal in animals: + if animal.startswith(word): + if animal in animal_family: + family = animal_family[animal] + family_color = family_colors.get(family, "default") + + display = HTML( + "%s: (<" + + family_color + + ">%s)" + ) % (animal, family) + else: + display = animal + + yield Completion( + animal, + start_position=-len(word), + display=display, + display_meta=meta.get(animal), + ) + + +def main(): + # Simple completion menu. + print("(The completion menu displays colors.)") + prompt("Type an animal: ", completer=AnimalCompleter()) + + # Multi-column menu. + prompt( + "Type an animal: ", + completer=AnimalCompleter(), + complete_style=CompleteStyle.MULTI_COLUMN, + ) + + # Readline-like + prompt( + "Type an animal: ", + completer=AnimalCompleter(), + complete_style=CompleteStyle.READLINE_LIKE, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/colored-completions.py b/examples/prompts/auto-completion/colored-completions.py new file mode 100755 index 0000000..9c5cba3 --- /dev/null +++ b/examples/prompts/auto-completion/colored-completions.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +""" +Demonstration of a custom completer class and the possibility of styling +completions independently. +""" +from prompt_toolkit.completion import Completer, Completion +from prompt_toolkit.output.color_depth import ColorDepth +from prompt_toolkit.shortcuts import CompleteStyle, prompt + +colors = [ + "red", + "blue", + "green", + "orange", + "purple", + "yellow", + "cyan", + "magenta", + "pink", +] + + +class ColorCompleter(Completer): + def get_completions(self, document, complete_event): + word = document.get_word_before_cursor() + for color in colors: + if color.startswith(word): + yield Completion( + color, + start_position=-len(word), + style="fg:" + color, + selected_style="fg:white bg:" + color, + ) + + +def main(): + # Simple completion menu. + print("(The completion menu displays colors.)") + prompt("Type a color: ", completer=ColorCompleter()) + + # Multi-column menu. + prompt( + "Type a color: ", + completer=ColorCompleter(), + complete_style=CompleteStyle.MULTI_COLUMN, + ) + + # Readline-like + prompt( + "Type a color: ", + completer=ColorCompleter(), + complete_style=CompleteStyle.READLINE_LIKE, + ) + + # Prompt with true color output. + message = [ + ("#cc2244", "T"), + ("#bb4444", "r"), + ("#996644", "u"), + ("#cc8844", "e "), + ("#ccaa44", "C"), + ("#bbaa44", "o"), + ("#99aa44", "l"), + ("#778844", "o"), + ("#55aa44", "r "), + ("#33aa44", "p"), + ("#11aa44", "r"), + ("#11aa66", "o"), + ("#11aa88", "m"), + ("#11aaaa", "p"), + ("#11aacc", "t"), + ("#11aaee", ": "), + ] + prompt(message, completer=ColorCompleter(), color_depth=ColorDepth.TRUE_COLOR) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/combine-multiple-completers.py b/examples/prompts/auto-completion/combine-multiple-completers.py new file mode 100755 index 0000000..511988b --- /dev/null +++ b/examples/prompts/auto-completion/combine-multiple-completers.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +""" +Example of multiple individual completers that are combined into one. +""" +from prompt_toolkit import prompt +from prompt_toolkit.completion import WordCompleter, merge_completers + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + +color_completer = WordCompleter( + [ + "red", + "green", + "blue", + "yellow", + "white", + "black", + "orange", + "gray", + "pink", + "purple", + "cyan", + "magenta", + "violet", + ], + ignore_case=True, +) + + +def main(): + completer = merge_completers([animal_completer, color_completer]) + + text = prompt( + "Give some animals: ", completer=completer, complete_while_typing=False + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/fuzzy-custom-completer.py b/examples/prompts/auto-completion/fuzzy-custom-completer.py new file mode 100755 index 0000000..fd9a7d7 --- /dev/null +++ b/examples/prompts/auto-completion/fuzzy-custom-completer.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +""" +Demonstration of a custom completer wrapped in a `FuzzyCompleter` for fuzzy +matching. +""" +from prompt_toolkit.completion import Completer, Completion, FuzzyCompleter +from prompt_toolkit.shortcuts import CompleteStyle, prompt + +colors = [ + "red", + "blue", + "green", + "orange", + "purple", + "yellow", + "cyan", + "magenta", + "pink", +] + + +class ColorCompleter(Completer): + def get_completions(self, document, complete_event): + word = document.get_word_before_cursor() + for color in colors: + if color.startswith(word): + yield Completion( + color, + start_position=-len(word), + style="fg:" + color, + selected_style="fg:white bg:" + color, + ) + + +def main(): + # Simple completion menu. + print("(The completion menu displays colors.)") + prompt("Type a color: ", completer=FuzzyCompleter(ColorCompleter())) + + # Multi-column menu. + prompt( + "Type a color: ", + completer=FuzzyCompleter(ColorCompleter()), + complete_style=CompleteStyle.MULTI_COLUMN, + ) + + # Readline-like + prompt( + "Type a color: ", + completer=FuzzyCompleter(ColorCompleter()), + complete_style=CompleteStyle.READLINE_LIKE, + ) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/fuzzy-word-completer.py b/examples/prompts/auto-completion/fuzzy-word-completer.py new file mode 100755 index 0000000..329c0c1 --- /dev/null +++ b/examples/prompts/auto-completion/fuzzy-word-completer.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +""" +Autocompletion example. + +Press [Tab] to complete the current word. +- The first Tab press fills in the common part of all completions + and shows all the completions. (In the menu) +- Any following tab press cycles through all the possible completions. +""" +from prompt_toolkit.completion import FuzzyWordCompleter +from prompt_toolkit.shortcuts import prompt + +animal_completer = FuzzyWordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ] +) + + +def main(): + text = prompt( + "Give some animals: ", completer=animal_completer, complete_while_typing=True + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/multi-column-autocompletion-with-meta.py b/examples/prompts/auto-completion/multi-column-autocompletion-with-meta.py new file mode 100755 index 0000000..5ba3ab5 --- /dev/null +++ b/examples/prompts/auto-completion/multi-column-autocompletion-with-meta.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python +""" +Autocompletion example that shows meta-information alongside the completions. +""" +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.shortcuts import CompleteStyle, prompt + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + ], + meta_dict={ + "alligator": "An alligator is a crocodilian in the genus Alligator of the family Alligatoridae.", + "ant": "Ants are eusocial insects of the family Formicidae", + "ape": "Apes (Hominoidea) are a branch of Old World tailless anthropoid catarrhine primates ", + "bat": "Bats are mammals of the order Chiroptera", + }, + ignore_case=True, +) + + +def main(): + text = prompt( + "Give some animals: ", + completer=animal_completer, + complete_style=CompleteStyle.MULTI_COLUMN, + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/multi-column-autocompletion.py b/examples/prompts/auto-completion/multi-column-autocompletion.py new file mode 100755 index 0000000..7fcfc52 --- /dev/null +++ b/examples/prompts/auto-completion/multi-column-autocompletion.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +""" +Similar to the autocompletion example. But display all the completions in multiple columns. +""" +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.shortcuts import CompleteStyle, prompt + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + + +def main(): + text = prompt( + "Give some animals: ", + completer=animal_completer, + complete_style=CompleteStyle.MULTI_COLUMN, + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/nested-autocompletion.py b/examples/prompts/auto-completion/nested-autocompletion.py new file mode 100755 index 0000000..cd85b8c --- /dev/null +++ b/examples/prompts/auto-completion/nested-autocompletion.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python +""" +Example of nested autocompletion. +""" +from prompt_toolkit import prompt +from prompt_toolkit.completion import NestedCompleter + +completer = NestedCompleter.from_nested_dict( + { + "show": {"version": None, "clock": None, "ip": {"interface": {"brief": None}}}, + "exit": None, + } +) + + +def main(): + text = prompt("Type a command: ", completer=completer) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-completion/slow-completions.py b/examples/prompts/auto-completion/slow-completions.py new file mode 100755 index 0000000..cce9d59 --- /dev/null +++ b/examples/prompts/auto-completion/slow-completions.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +""" +An example of how to deal with slow auto completion code. + +- Running the completions in a thread is possible by wrapping the + `Completer` object in a `ThreadedCompleter`. This makes sure that the + ``get_completions`` generator is executed in a background thread. + + For the `prompt` shortcut, we don't have to wrap the completer ourselves. + Passing `complete_in_thread=True` is sufficient. + +- We also set a `loading` boolean in the completer function to keep track of + when the completer is running, and display this in the toolbar. +""" +import time + +from prompt_toolkit.completion import Completer, Completion +from prompt_toolkit.shortcuts import CompleteStyle, prompt + +WORDS = [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", +] + + +class SlowCompleter(Completer): + """ + This is a completer that's very slow. + """ + + def __init__(self): + self.loading = 0 + + def get_completions(self, document, complete_event): + # Keep count of how many completion generators are running. + self.loading += 1 + word_before_cursor = document.get_word_before_cursor() + + try: + for word in WORDS: + if word.startswith(word_before_cursor): + time.sleep(0.2) # Simulate slowness. + yield Completion(word, -len(word_before_cursor)) + + finally: + # We use try/finally because this generator can be closed if the + # input text changes before all completions are generated. + self.loading -= 1 + + +def main(): + # We wrap it in a ThreadedCompleter, to make sure it runs in a different + # thread. That way, we don't block the UI while running the completions. + slow_completer = SlowCompleter() + + # Add a bottom toolbar that display when completions are loading. + def bottom_toolbar(): + return " Loading completions... " if slow_completer.loading > 0 else "" + + # Display prompt. + text = prompt( + "Give some animals: ", + completer=slow_completer, + complete_in_thread=True, + complete_while_typing=True, + bottom_toolbar=bottom_toolbar, + complete_style=CompleteStyle.MULTI_COLUMN, + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/auto-suggestion.py b/examples/prompts/auto-suggestion.py new file mode 100755 index 0000000..6660777 --- /dev/null +++ b/examples/prompts/auto-suggestion.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +""" +Simple example of a CLI that demonstrates fish-style auto suggestion. + +When you type some input, it will match the input against the history. If One +entry of the history starts with the given input, then it will show the +remaining part as a suggestion. Pressing the right arrow will insert this +suggestion. +""" +from prompt_toolkit import PromptSession +from prompt_toolkit.auto_suggest import AutoSuggestFromHistory +from prompt_toolkit.history import InMemoryHistory + + +def main(): + # Create some history first. (Easy for testing.) + history = InMemoryHistory() + history.append_string("import os") + history.append_string('print("hello")') + history.append_string('print("world")') + history.append_string("import path") + + # Print help. + print("This CLI has fish-style auto-suggestion enable.") + print('Type for instance "pri", then you\'ll see a suggestion.') + print("Press the right arrow to insert the suggestion.") + print("Press Control-C to retry. Control-D to exit.") + print() + + session = PromptSession( + history=history, + auto_suggest=AutoSuggestFromHistory(), + enable_history_search=True, + ) + + while True: + try: + text = session.prompt("Say something: ") + except KeyboardInterrupt: + pass # Ctrl-C pressed. Try again. + else: + break + + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/autocorrection.py b/examples/prompts/autocorrection.py new file mode 100755 index 0000000..6378132 --- /dev/null +++ b/examples/prompts/autocorrection.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +Example of implementing auto correction while typing. + +The word "impotr" will be corrected when the user types a space afterwards. +""" +from prompt_toolkit import prompt +from prompt_toolkit.key_binding import KeyBindings + +# Database of words to be replaced by typing. +corrections = { + "impotr": "import", + "wolrd": "world", +} + + +def main(): + # We start with a `KeyBindings` for our extra key bindings. + bindings = KeyBindings() + + # We add a custom key binding to space. + @bindings.add(" ") + def _(event): + """ + When space is pressed, we check the word before the cursor, and + autocorrect that. + """ + b = event.app.current_buffer + w = b.document.get_word_before_cursor() + + if w is not None: + if w in corrections: + b.delete_before_cursor(count=len(w)) + b.insert_text(corrections[w]) + + b.insert_text(" ") + + # Read input. + text = prompt("Say something: ", key_bindings=bindings) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/bottom-toolbar.py b/examples/prompts/bottom-toolbar.py new file mode 100755 index 0000000..4980e5b --- /dev/null +++ b/examples/prompts/bottom-toolbar.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python +""" +A few examples of displaying a bottom toolbar. + +The ``prompt`` function takes a ``bottom_toolbar`` attribute. +This can be any kind of formatted text (plain text, HTML or ANSI), or +it can be a callable that takes an App and returns an of these. + +The bottom toolbar will always receive the style 'bottom-toolbar', and the text +inside will get 'bottom-toolbar.text'. These can be used to change the default +style. +""" +import time + +from prompt_toolkit import prompt +from prompt_toolkit.formatted_text import ANSI, HTML +from prompt_toolkit.styles import Style + + +def main(): + # Example 1: fixed text. + text = prompt("Say something: ", bottom_toolbar="This is a toolbar") + print("You said: %s" % text) + + # Example 2: fixed text from a callable: + def get_toolbar(): + return "Bottom toolbar: time=%r" % time.time() + + text = prompt("Say something: ", bottom_toolbar=get_toolbar, refresh_interval=0.5) + print("You said: %s" % text) + + # Example 3: Using HTML: + text = prompt( + "Say something: ", + bottom_toolbar=HTML( + '(html) This is a ' + ), + ) + print("You said: %s" % text) + + # Example 4: Using ANSI: + text = prompt( + "Say something: ", + bottom_toolbar=ANSI( + "(ansi): \x1b[1mThis\x1b[0m \x1b[4mis\x1b[0m a \x1b[91mtoolbar" + ), + ) + print("You said: %s" % text) + + # Example 5: styling differently. + style = Style.from_dict( + { + "bottom-toolbar": "#aaaa00 bg:#ff0000", + "bottom-toolbar.text": "#aaaa44 bg:#aa4444", + } + ) + + text = prompt("Say something: ", bottom_toolbar="This is a toolbar", style=style) + print("You said: %s" % text) + + # Example 6: Using a list of tokens. + def get_bottom_toolbar(): + return [ + ("", " "), + ("bg:#ff0000 fg:#000000", "This"), + ("", " is a "), + ("bg:#ff0000 fg:#000000", "toolbar"), + ("", ". "), + ] + + text = prompt("Say something: ", bottom_toolbar=get_bottom_toolbar) + print("You said: %s" % text) + + # Example 7: multiline fixed text. + text = prompt("Say something: ", bottom_toolbar="This is\na multiline toolbar") + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/clock-input.py b/examples/prompts/clock-input.py new file mode 100755 index 0000000..e43abd8 --- /dev/null +++ b/examples/prompts/clock-input.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +""" +Example of a 'dynamic' prompt. On that shows the current time in the prompt. +""" +import datetime + +from prompt_toolkit.shortcuts import prompt + + +def get_prompt(): + "Tokens to be shown before the prompt." + now = datetime.datetime.now() + return [ + ("bg:#008800 #ffffff", f"{now.hour}:{now.minute}:{now.second}"), + ("bg:cornsilk fg:maroon", " Enter something: "), + ] + + +def main(): + result = prompt(get_prompt, refresh_interval=0.5) + print("You said: %s" % result) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/colored-prompt.py b/examples/prompts/colored-prompt.py new file mode 100755 index 0000000..1e63e29 --- /dev/null +++ b/examples/prompts/colored-prompt.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +""" +Example of a colored prompt. +""" +from prompt_toolkit import prompt +from prompt_toolkit.formatted_text import ANSI, HTML +from prompt_toolkit.styles import Style + +style = Style.from_dict( + { + # Default style. + "": "#ff0066", + # Prompt. + "username": "#884444 italic", + "at": "#00aa00", + "colon": "#00aa00", + "pound": "#00aa00", + "host": "#000088 bg:#aaaaff", + "path": "#884444 underline", + # Make a selection reverse/underlined. + # (Use Control-Space to select.) + "selected-text": "reverse underline", + } +) + + +def example_1(): + """ + Style and list of (style, text) tuples. + """ + # Not that we can combine class names and inline styles. + prompt_fragments = [ + ("class:username", "john"), + ("class:at", "@"), + ("class:host", "localhost"), + ("class:colon", ":"), + ("class:path", "/user/john"), + ("bg:#00aa00 #ffffff", "#"), + ("", " "), + ] + + answer = prompt(prompt_fragments, style=style) + print("You said: %s" % answer) + + +def example_2(): + """ + Using HTML for the formatting. + """ + answer = prompt( + HTML( + "john@" + "localhost" + ":" + "/user/john" + ' ' + ), + style=style, + ) + print("You said: %s" % answer) + + +def example_3(): + """ + Using ANSI for the formatting. + """ + answer = prompt( + ANSI( + "\x1b[31mjohn\x1b[0m@" + "\x1b[44mlocalhost\x1b[0m:" + "\x1b[4m/user/john\x1b[0m" + "# " + ) + ) + print("You said: %s" % answer) + + +if __name__ == "__main__": + example_1() + example_2() + example_3() diff --git a/examples/prompts/confirmation-prompt.py b/examples/prompts/confirmation-prompt.py new file mode 100755 index 0000000..bd52b9e --- /dev/null +++ b/examples/prompts/confirmation-prompt.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +""" +Example of a confirmation prompt. +""" +from prompt_toolkit.shortcuts import confirm + +if __name__ == "__main__": + answer = confirm("Should we do that?") + print("You said: %s" % answer) diff --git a/examples/prompts/cursor-shapes.py b/examples/prompts/cursor-shapes.py new file mode 100755 index 0000000..e668243 --- /dev/null +++ b/examples/prompts/cursor-shapes.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python +""" +Example of cursor shape configurations. +""" +from prompt_toolkit import prompt +from prompt_toolkit.cursor_shapes import CursorShape, ModalCursorShapeConfig + +# NOTE: We pass `enable_suspend=True`, so that we can easily see what happens +# to the cursor shapes when the application is suspended. + +prompt("(block): ", cursor=CursorShape.BLOCK, enable_suspend=True) +prompt("(underline): ", cursor=CursorShape.UNDERLINE, enable_suspend=True) +prompt("(beam): ", cursor=CursorShape.BEAM, enable_suspend=True) +prompt( + "(modal - according to vi input mode): ", + cursor=ModalCursorShapeConfig(), + vi_mode=True, + enable_suspend=True, +) diff --git a/examples/prompts/custom-key-binding.py b/examples/prompts/custom-key-binding.py new file mode 100755 index 0000000..32d889e --- /dev/null +++ b/examples/prompts/custom-key-binding.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +""" +Example of adding a custom key binding to a prompt. +""" +import asyncio + +from prompt_toolkit import prompt +from prompt_toolkit.application import in_terminal, run_in_terminal +from prompt_toolkit.key_binding import KeyBindings + + +def main(): + # We start with a `KeyBindings` of default key bindings. + bindings = KeyBindings() + + # Add our own key binding. + @bindings.add("f4") + def _(event): + """ + When F4 has been pressed. Insert "hello world" as text. + """ + event.app.current_buffer.insert_text("hello world") + + @bindings.add("x", "y") + def _(event): + """ + (Useless, but for demoing.) + Typing 'xy' will insert 'z'. + + Note that when you type for instance 'xa', the insertion of 'x' is + postponed until the 'a' is typed. because we don't know earlier whether + or not a 'y' will follow. However, prompt-toolkit should already give + some visual feedback of the typed character. + """ + event.app.current_buffer.insert_text("z") + + @bindings.add("a", "b", "c") + def _(event): + "Typing 'abc' should insert 'd'." + event.app.current_buffer.insert_text("d") + + @bindings.add("c-t") + def _(event): + """ + Print 'hello world' in the terminal when ControlT is pressed. + + We use ``run_in_terminal``, because that ensures that the prompt is + hidden right before ``print_hello`` gets executed and it's drawn again + after it. (Otherwise this would destroy the output.) + """ + + def print_hello(): + print("hello world") + + run_in_terminal(print_hello) + + @bindings.add("c-k") + async def _(event): + """ + Example of asyncio coroutine as a key binding. + """ + try: + for i in range(5): + async with in_terminal(): + print("hello") + await asyncio.sleep(1) + except asyncio.CancelledError: + print("Prompt terminated before we completed.") + + # Read input. + print('Press F4 to insert "hello world", type "xy" to insert "z":') + text = prompt("> ", key_bindings=bindings) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/custom-lexer.py b/examples/prompts/custom-lexer.py new file mode 100755 index 0000000..c4c9fbe --- /dev/null +++ b/examples/prompts/custom-lexer.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +""" +An example of a custom lexer that prints the input text in random colors. +""" +from prompt_toolkit.lexers import Lexer +from prompt_toolkit.shortcuts import prompt +from prompt_toolkit.styles.named_colors import NAMED_COLORS + + +class RainbowLexer(Lexer): + def lex_document(self, document): + colors = sorted(NAMED_COLORS, key=NAMED_COLORS.get) + + def get_line(lineno): + return [ + (colors[i % len(colors)], c) + for i, c in enumerate(document.lines[lineno]) + ] + + return get_line + + +def main(): + answer = prompt("Give me some input: ", lexer=RainbowLexer()) + print("You said: %s" % answer) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/custom-vi-operator-and-text-object.py b/examples/prompts/custom-vi-operator-and-text-object.py new file mode 100755 index 0000000..7478afc --- /dev/null +++ b/examples/prompts/custom-vi-operator-and-text-object.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python +""" +Example of adding a custom Vi operator and text object. +(Note that this API is not guaranteed to remain stable.) +""" +from prompt_toolkit import prompt +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.key_binding.bindings.vi import ( + TextObject, + create_operator_decorator, + create_text_object_decorator, +) + + +def main(): + # We start with a `Registry` of default key bindings. + bindings = KeyBindings() + + # Create the decorators to be used for registering text objects and + # operators in this registry. + operator = create_operator_decorator(bindings) + text_object = create_text_object_decorator(bindings) + + # Create a custom operator. + + @operator("R") + def _(event, text_object): + "Custom operator that reverses text." + buff = event.current_buffer + + # Get relative start/end coordinates. + start, end = text_object.operator_range(buff.document) + start += buff.cursor_position + end += buff.cursor_position + + text = buff.text[start:end] + text = "".join(reversed(text)) + + event.app.current_buffer.text = buff.text[:start] + text + buff.text[end:] + + # Create a text object. + + @text_object("A") + def _(event): + "A custom text object that involves everything." + # Note that a `TextObject` has coordinates, relative to the cursor position. + buff = event.current_buffer + return TextObject( + -buff.document.cursor_position, # The start. + len(buff.text) - buff.document.cursor_position, + ) # The end. + + # Read input. + print('There is a custom text object "A" that applies to everything') + print('and a custom operator "r" that reverses the text object.\n') + + print("Things that are possible:") + print("- Riw - reverse inner word.") + print("- yA - yank everything.") + print("- RA - reverse everything.") + + text = prompt( + "> ", default="hello world", key_bindings=bindings, editing_mode=EditingMode.VI + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/enforce-tty-input-output.py b/examples/prompts/enforce-tty-input-output.py new file mode 100755 index 0000000..93b43ee --- /dev/null +++ b/examples/prompts/enforce-tty-input-output.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +""" +This will display a prompt that will always use the terminal for input and +output, even if sys.stdin/stdout are connected to pipes. + +For testing, run as: + cat /dev/null | python ./enforce-tty-input-output.py > /dev/null +""" +from prompt_toolkit.application import create_app_session_from_tty +from prompt_toolkit.shortcuts import prompt + +with create_app_session_from_tty(): + prompt(">") diff --git a/examples/prompts/fancy-zsh-prompt.py b/examples/prompts/fancy-zsh-prompt.py new file mode 100755 index 0000000..4761c08 --- /dev/null +++ b/examples/prompts/fancy-zsh-prompt.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python +""" +Example of the fancy ZSH prompt that @anki-code was using. + +The theme is coming from the xonsh plugin from the xxh project: +https://github.com/xxh/xxh-plugin-xonsh-theme-bar + +See: +- https://github.com/xonsh/xonsh/issues/3356 +- https://github.com/prompt-toolkit/python-prompt-toolkit/issues/1111 +""" +import datetime + +from prompt_toolkit import prompt +from prompt_toolkit.application import get_app +from prompt_toolkit.formatted_text import ( + HTML, + fragment_list_width, + merge_formatted_text, + to_formatted_text, +) +from prompt_toolkit.styles import Style + +style = Style.from_dict( + { + "username": "#aaaaaa italic", + "path": "#ffffff bold", + "branch": "bg:#666666", + "branch exclamation-mark": "#ff0000", + "env": "bg:#666666", + "left-part": "bg:#444444", + "right-part": "bg:#444444", + "padding": "bg:#444444", + } +) + + +def get_prompt() -> HTML: + """ + Build the prompt dynamically every time its rendered. + """ + left_part = HTML( + "" + " root " + " abc " + "~/.oh-my-zsh/themes" + "" + ) + right_part = HTML( + " " + " master! " + " py36 " + " " + "" + ) % (datetime.datetime.now().isoformat(),) + + used_width = sum( + [ + fragment_list_width(to_formatted_text(left_part)), + fragment_list_width(to_formatted_text(right_part)), + ] + ) + + total_width = get_app().output.get_size().columns + padding_size = total_width - used_width + + padding = HTML("%s") % (" " * padding_size,) + + return merge_formatted_text([left_part, padding, right_part, "\n", "# "]) + + +def main() -> None: + while True: + answer = prompt(get_prompt, style=style, refresh_interval=1) + print("You said: %s" % answer) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/finalterm-shell-integration.py b/examples/prompts/finalterm-shell-integration.py new file mode 100755 index 0000000..30c7a7f --- /dev/null +++ b/examples/prompts/finalterm-shell-integration.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python +""" +Mark the start and end of the prompt with Final term (iterm2) escape sequences. +See: https://iterm2.com/finalterm.html +""" +import sys + +from prompt_toolkit import prompt +from prompt_toolkit.formatted_text import ANSI + +BEFORE_PROMPT = "\033]133;A\a" +AFTER_PROMPT = "\033]133;B\a" +BEFORE_OUTPUT = "\033]133;C\a" +AFTER_OUTPUT = ( + "\033]133;D;{command_status}\a" # command_status is the command status, 0-255 +) + + +def get_prompt_text(): + # Generate the text fragments for the prompt. + # Important: use the `ZeroWidthEscape` fragment only if you are sure that + # writing this as raw text to the output will not introduce any + # cursor movements. + return [ + ("[ZeroWidthEscape]", BEFORE_PROMPT), + ("", "Say something: # "), + ("[ZeroWidthEscape]", AFTER_PROMPT), + ] + + +if __name__ == "__main__": + # Option 1: Using a `get_prompt_text` function: + answer = prompt(get_prompt_text) + + # Option 2: Using ANSI escape sequences. + before = "\001" + BEFORE_PROMPT + "\002" + after = "\001" + AFTER_PROMPT + "\002" + answer = prompt(ANSI(f"{before}Say something: # {after}")) + + # Output. + sys.stdout.write(BEFORE_OUTPUT) + print("You said: %s" % answer) + sys.stdout.write(AFTER_OUTPUT.format(command_status=0)) diff --git a/examples/prompts/get-input-vi-mode.py b/examples/prompts/get-input-vi-mode.py new file mode 100755 index 0000000..566ffd5 --- /dev/null +++ b/examples/prompts/get-input-vi-mode.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python +from prompt_toolkit import prompt + +if __name__ == "__main__": + print("You have Vi keybindings here. Press [Esc] to go to navigation mode.") + answer = prompt("Give me some input: ", multiline=False, vi_mode=True) + print("You said: %s" % answer) diff --git a/examples/prompts/get-input-with-default.py b/examples/prompts/get-input-with-default.py new file mode 100755 index 0000000..67446d5 --- /dev/null +++ b/examples/prompts/get-input-with-default.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +""" +Example of a call to `prompt` with a default value. +The input is pre-filled, but the user can still edit the default. +""" +import getpass + +from prompt_toolkit import prompt + +if __name__ == "__main__": + answer = prompt("What is your name: ", default="%s" % getpass.getuser()) + print("You said: %s" % answer) diff --git a/examples/prompts/get-input.py b/examples/prompts/get-input.py new file mode 100755 index 0000000..5529bbb --- /dev/null +++ b/examples/prompts/get-input.py @@ -0,0 +1,9 @@ +#!/usr/bin/env python +""" +The most simple prompt example. +""" +from prompt_toolkit import prompt + +if __name__ == "__main__": + answer = prompt("Give me some input: ") + print("You said: %s" % answer) diff --git a/examples/prompts/get-multiline-input.py b/examples/prompts/get-multiline-input.py new file mode 100755 index 0000000..eda35be --- /dev/null +++ b/examples/prompts/get-multiline-input.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +from prompt_toolkit import prompt +from prompt_toolkit.formatted_text import HTML + + +def prompt_continuation(width, line_number, wrap_count): + """ + The continuation: display line numbers and '->' before soft wraps. + + Notice that we can return any kind of formatted text from here. + + The prompt continuation doesn't have to be the same width as the prompt + which is displayed before the first line, but in this example we choose to + align them. The `width` input that we receive here represents the width of + the prompt. + """ + if wrap_count > 0: + return " " * (width - 3) + "-> " + else: + text = ("- %i - " % (line_number + 1)).rjust(width) + return HTML("%s") % text + + +if __name__ == "__main__": + print("Press [Meta+Enter] or [Esc] followed by [Enter] to accept input.") + answer = prompt( + "Multiline input: ", multiline=True, prompt_continuation=prompt_continuation + ) + print("You said: %s" % answer) diff --git a/examples/prompts/get-password-with-toggle-display-shortcut.py b/examples/prompts/get-password-with-toggle-display-shortcut.py new file mode 100755 index 0000000..b89cb41 --- /dev/null +++ b/examples/prompts/get-password-with-toggle-display-shortcut.py @@ -0,0 +1,28 @@ +#!/usr/bin/env python +""" +get_password function that displays asterisks instead of the actual characters. +With the addition of a ControlT shortcut to hide/show the input. +""" +from prompt_toolkit import prompt +from prompt_toolkit.filters import Condition +from prompt_toolkit.key_binding import KeyBindings + + +def main(): + hidden = [True] # Nonlocal + bindings = KeyBindings() + + @bindings.add("c-t") + def _(event): + "When ControlT has been pressed, toggle visibility." + hidden[0] = not hidden[0] + + print("Type Control-T to toggle password visible.") + password = prompt( + "Password: ", is_password=Condition(lambda: hidden[0]), key_bindings=bindings + ) + print("You said: %s" % password) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/get-password.py b/examples/prompts/get-password.py new file mode 100755 index 0000000..1c9517c --- /dev/null +++ b/examples/prompts/get-password.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +from prompt_toolkit import prompt + +if __name__ == "__main__": + password = prompt("Password: ", is_password=True) + print("You said: %s" % password) diff --git a/examples/prompts/history/persistent-history.py b/examples/prompts/history/persistent-history.py new file mode 100755 index 0000000..2bdb758 --- /dev/null +++ b/examples/prompts/history/persistent-history.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python +""" +Simple example of a CLI that keeps a persistent history of all the entered +strings in a file. When you run this script for a second time, pressing +arrow-up will go back in history. +""" +from prompt_toolkit import PromptSession +from prompt_toolkit.history import FileHistory + + +def main(): + our_history = FileHistory(".example-history-file") + + # The history needs to be passed to the `PromptSession`. It can't be passed + # to the `prompt` call because only one history can be used during a + # session. + session = PromptSession(history=our_history) + + while True: + text = session.prompt("Say something: ") + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/history/slow-history.py b/examples/prompts/history/slow-history.py new file mode 100755 index 0000000..5b6a7a2 --- /dev/null +++ b/examples/prompts/history/slow-history.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python +""" +Simple example of a custom, very slow history, that is loaded asynchronously. + +By wrapping it in `ThreadedHistory`, the history will load in the background +without blocking any user interaction. +""" +import time + +from prompt_toolkit import PromptSession +from prompt_toolkit.history import History, ThreadedHistory + + +class SlowHistory(History): + """ + Example class that loads the history very slowly... + """ + + def load_history_strings(self): + for i in range(1000): + time.sleep(1) # Emulate slowness. + yield f"item-{i}" + + def store_string(self, string): + pass # Don't store strings. + + +def main(): + print( + "Asynchronous loading of history. Notice that the up-arrow will work " + "for as far as the completions are loaded.\n" + "Even when the input is accepted, loading will continue in the " + "background and when the next prompt is displayed.\n" + ) + our_history = ThreadedHistory(SlowHistory()) + + # The history needs to be passed to the `PromptSession`. It can't be passed + # to the `prompt` call because only one history can be used during a + # session. + session = PromptSession(history=our_history) + + while True: + text = session.prompt("Say something: ") + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/html-input.py b/examples/prompts/html-input.py new file mode 100755 index 0000000..4c51737 --- /dev/null +++ b/examples/prompts/html-input.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +""" +Simple example of a syntax-highlighted HTML input line. +(This requires Pygments to be installed.) +""" +from pygments.lexers.html import HtmlLexer + +from prompt_toolkit import prompt +from prompt_toolkit.lexers import PygmentsLexer + + +def main(): + text = prompt("Enter HTML: ", lexer=PygmentsLexer(HtmlLexer)) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/input-validation.py b/examples/prompts/input-validation.py new file mode 100755 index 0000000..d8bd3ee --- /dev/null +++ b/examples/prompts/input-validation.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +""" +Simple example of input validation. +""" +from prompt_toolkit import prompt +from prompt_toolkit.validation import Validator + + +def is_valid_email(text): + return "@" in text + + +validator = Validator.from_callable( + is_valid_email, + error_message="Not a valid e-mail address (Does not contain an @).", + move_cursor_to_end=True, +) + + +def main(): + # Validate when pressing ENTER. + text = prompt( + "Enter e-mail address: ", validator=validator, validate_while_typing=False + ) + print("You said: %s" % text) + + # While typing + text = prompt( + "Enter e-mail address: ", validator=validator, validate_while_typing=True + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/inputhook.py b/examples/prompts/inputhook.py new file mode 100755 index 0000000..7cbfe18 --- /dev/null +++ b/examples/prompts/inputhook.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python +""" +An example that demonstrates how inputhooks can be used in prompt-toolkit. + +An inputhook is a callback that an eventloop calls when it's idle. For +instance, readline calls `PyOS_InputHook`. This allows us to do other work in +the same thread, while waiting for input. Important however is that we give the +control back to prompt-toolkit when some input is ready to be processed. + +There are two ways to know when input is ready. One way is to poll +`InputHookContext.input_is_ready()`. Another way is to check for +`InputHookContext.fileno()` to be ready. In this example we do the latter. +""" +import gobject +import gtk +from pygments.lexers.python import PythonLexer + +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.patch_stdout import patch_stdout +from prompt_toolkit.shortcuts import PromptSession + + +def hello_world_window(): + """ + Create a GTK window with one 'Hello world' button. + """ + # Create a new window. + window = gtk.Window(gtk.WINDOW_TOPLEVEL) + window.set_border_width(50) + + # Create a new button with the label "Hello World". + button = gtk.Button("Hello World") + window.add(button) + + # Clicking the button prints some text. + def clicked(data): + print("Button clicked!") + + button.connect("clicked", clicked) + + # Display the window. + button.show() + window.show() + + +def inputhook(context): + """ + When the eventloop of prompt-toolkit is idle, call this inputhook. + + This will run the GTK main loop until the file descriptor + `context.fileno()` becomes ready. + + :param context: An `InputHookContext` instance. + """ + + def _main_quit(*a, **kw): + gtk.main_quit() + return False + + gobject.io_add_watch(context.fileno(), gobject.IO_IN, _main_quit) + gtk.main() + + +def main(): + # Create user interface. + hello_world_window() + + # Enable threading in GTK. (Otherwise, GTK will keep the GIL.) + gtk.gdk.threads_init() + + # Read input from the command line, using an event loop with this hook. + # We use `patch_stdout`, because clicking the button will print something; + # and that should print nicely 'above' the input line. + with patch_stdout(): + session = PromptSession( + "Python >>> ", inputhook=inputhook, lexer=PygmentsLexer(PythonLexer) + ) + result = session.prompt() + print("You said: %s" % result) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/mouse-support.py b/examples/prompts/mouse-support.py new file mode 100755 index 0000000..1e4ee76 --- /dev/null +++ b/examples/prompts/mouse-support.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +from prompt_toolkit import prompt + +if __name__ == "__main__": + print( + "This is multiline input. press [Meta+Enter] or [Esc] followed by [Enter] to accept input." + ) + print("You can click with the mouse in order to select text.") + answer = prompt("Multiline input: ", multiline=True, mouse_support=True) + print("You said: %s" % answer) diff --git a/examples/prompts/multiline-prompt.py b/examples/prompts/multiline-prompt.py new file mode 100755 index 0000000..d6a7698 --- /dev/null +++ b/examples/prompts/multiline-prompt.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +""" +Demonstration of how the input can be indented. +""" +from prompt_toolkit import prompt + +if __name__ == "__main__": + answer = prompt( + "Give me some input: (ESCAPE followed by ENTER to accept)\n > ", multiline=True + ) + print("You said: %s" % answer) diff --git a/examples/prompts/no-wrapping.py b/examples/prompts/no-wrapping.py new file mode 100755 index 0000000..371486e --- /dev/null +++ b/examples/prompts/no-wrapping.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +from prompt_toolkit import prompt + +if __name__ == "__main__": + answer = prompt("Give me some input: ", wrap_lines=False, multiline=True) + print("You said: %s" % answer) diff --git a/examples/prompts/operate-and-get-next.py b/examples/prompts/operate-and-get-next.py new file mode 100755 index 0000000..6ea4d79 --- /dev/null +++ b/examples/prompts/operate-and-get-next.py @@ -0,0 +1,18 @@ +#!/usr/bin/env python +""" +Demo of "operate-and-get-next". + +(Actually, this creates one prompt application, and keeps running the same app +over and over again. -- For now, this is the only way to get this working.) +""" +from prompt_toolkit.shortcuts import PromptSession + + +def main(): + session = PromptSession("prompt> ") + while True: + session.prompt() + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/patch-stdout.py b/examples/prompts/patch-stdout.py new file mode 100755 index 0000000..1c83524 --- /dev/null +++ b/examples/prompts/patch-stdout.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +""" +An example that demonstrates how `patch_stdout` works. + +This makes sure that output from other threads doesn't disturb the rendering of +the prompt, but instead is printed nicely above the prompt. +""" +import threading +import time + +from prompt_toolkit import prompt +from prompt_toolkit.patch_stdout import patch_stdout + + +def main(): + # Print a counter every second in another thread. + running = True + + def thread(): + i = 0 + while running: + i += 1 + print("i=%i" % i) + time.sleep(1) + + t = threading.Thread(target=thread) + t.daemon = True + t.start() + + # Now read the input. The print statements of the other thread + # should not disturb anything. + with patch_stdout(): + result = prompt("Say something: ") + print("You said: %s" % result) + + # Stop thread. + running = False + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/placeholder-text.py b/examples/prompts/placeholder-text.py new file mode 100755 index 0000000..35e1c6c --- /dev/null +++ b/examples/prompts/placeholder-text.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +""" +Example of a placeholder that's displayed as long as no input is given. +""" +from prompt_toolkit import prompt +from prompt_toolkit.formatted_text import HTML + +if __name__ == "__main__": + answer = prompt( + "Give me some input: ", + placeholder=HTML(''), + ) + print("You said: %s" % answer) diff --git a/examples/prompts/regular-language.py b/examples/prompts/regular-language.py new file mode 100755 index 0000000..cbe7256 --- /dev/null +++ b/examples/prompts/regular-language.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python +""" +This is an example of "prompt_toolkit.contrib.regular_languages" which +implements a little calculator. + +Type for instance:: + + > add 4 4 + > sub 4 4 + > sin 3.14 + +This example shows how you can define the grammar of a regular language and how +to use variables in this grammar with completers and tokens attached. +""" +import math + +from prompt_toolkit import prompt +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.contrib.regular_languages.compiler import compile +from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter +from prompt_toolkit.contrib.regular_languages.lexer import GrammarLexer +from prompt_toolkit.lexers import SimpleLexer +from prompt_toolkit.styles import Style + +operators1 = ["add", "sub", "div", "mul"] +operators2 = ["cos", "sin"] + + +def create_grammar(): + return compile( + r""" + (\s* (?P[a-z]+) \s+ (?P[0-9.]+) \s+ (?P[0-9.]+) \s*) | + (\s* (?P[a-z]+) \s+ (?P[0-9.]+) \s*) + """ + ) + + +example_style = Style.from_dict( + { + "operator": "#33aa33 bold", + "number": "#ff0000 bold", + "trailing-input": "bg:#662222 #ffffff", + } +) + + +if __name__ == "__main__": + g = create_grammar() + + lexer = GrammarLexer( + g, + lexers={ + "operator1": SimpleLexer("class:operator"), + "operator2": SimpleLexer("class:operator"), + "var1": SimpleLexer("class:number"), + "var2": SimpleLexer("class:number"), + }, + ) + + completer = GrammarCompleter( + g, + { + "operator1": WordCompleter(operators1), + "operator2": WordCompleter(operators2), + }, + ) + + try: + # REPL loop. + while True: + # Read input and parse the result. + text = prompt( + "Calculate: ", lexer=lexer, completer=completer, style=example_style + ) + m = g.match(text) + if m: + vars = m.variables() + else: + print("Invalid command\n") + continue + + print(vars) + if vars.get("operator1") or vars.get("operator2"): + try: + var1 = float(vars.get("var1", 0)) + var2 = float(vars.get("var2", 0)) + except ValueError: + print("Invalid command (2)\n") + continue + + # Turn the operator string into a function. + operator = { + "add": (lambda a, b: a + b), + "sub": (lambda a, b: a - b), + "mul": (lambda a, b: a * b), + "div": (lambda a, b: a / b), + "sin": (lambda a, b: math.sin(a)), + "cos": (lambda a, b: math.cos(a)), + }[vars.get("operator1") or vars.get("operator2")] + + # Execute and print the result. + print("Result: %s\n" % (operator(var1, var2))) + + elif vars.get("operator2"): + print("Operator 2") + + except EOFError: + pass diff --git a/examples/prompts/rprompt.py b/examples/prompts/rprompt.py new file mode 100755 index 0000000..f7656b7 --- /dev/null +++ b/examples/prompts/rprompt.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +""" +Example of a right prompt. This is an additional prompt that is displayed on +the right side of the terminal. It will be hidden automatically when the input +is long enough to cover the right side of the terminal. + +This is similar to RPROMPT is Zsh. +""" +from prompt_toolkit import prompt +from prompt_toolkit.formatted_text import ANSI, HTML +from prompt_toolkit.styles import Style + +example_style = Style.from_dict( + { + # The 'rprompt' gets by default the 'rprompt' class. We can use this + # for the styling. + "rprompt": "bg:#ff0066 #ffffff", + } +) + + +def get_rprompt_text(): + return [ + ("", " "), + ("underline", ""), + ("", " "), + ] + + +def main(): + # Option 1: pass a string to 'rprompt': + answer = prompt("> ", rprompt=" ", style=example_style) + print("You said: %s" % answer) + + # Option 2: pass HTML: + answer = prompt("> ", rprompt=HTML(" <rprompt> "), style=example_style) + print("You said: %s" % answer) + + # Option 3: pass ANSI: + answer = prompt( + "> ", rprompt=ANSI(" \x1b[4m\x1b[0m "), style=example_style + ) + print("You said: %s" % answer) + + # Option 4: Pass a callable. (This callable can either return plain text, + # an HTML object, an ANSI object or a list of (style, text) + # tuples. + answer = prompt("> ", rprompt=get_rprompt_text, style=example_style) + print("You said: %s" % answer) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/swap-light-and-dark-colors.py b/examples/prompts/swap-light-and-dark-colors.py new file mode 100755 index 0000000..e602449 --- /dev/null +++ b/examples/prompts/swap-light-and-dark-colors.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +""" +Demonstration of swapping light/dark colors in prompt_toolkit using the +`swap_light_and_dark_colors` parameter. + +Notice that this doesn't swap foreground and background like "reverse" does. It +turns light green into dark green and the other way around. Foreground and +background are independent of each other. +""" +from pygments.lexers.html import HtmlLexer + +from prompt_toolkit import prompt +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.filters import Condition +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.key_binding import KeyBindings +from prompt_toolkit.lexers import PygmentsLexer + +html_completer = WordCompleter( + [ + "", + "
", + "", + "", + "", + "
  • ", + "", + "
      ", + "

      ", + "", + "", + "", + "
        ", + ], + ignore_case=True, +) + + +def main(): + swapped = [False] # Nonlocal + bindings = KeyBindings() + + @bindings.add("c-t") + def _(event): + "When ControlT has been pressed, toggle light/dark colors." + swapped[0] = not swapped[0] + + def bottom_toolbar(): + if swapped[0]: + on = "on=true" + else: + on = "on=false" + + return ( + HTML( + 'Press ' + "to swap between dark/light colors. " + '' + ) + % on + ) + + text = prompt( + HTML(': '), + completer=html_completer, + complete_while_typing=True, + bottom_toolbar=bottom_toolbar, + key_bindings=bindings, + lexer=PygmentsLexer(HtmlLexer), + swap_light_and_dark_colors=Condition(lambda: swapped[0]), + ) + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/prompts/switch-between-vi-emacs.py b/examples/prompts/switch-between-vi-emacs.py new file mode 100755 index 0000000..249c7ef --- /dev/null +++ b/examples/prompts/switch-between-vi-emacs.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python +""" +Example that displays how to switch between Emacs and Vi input mode. + +""" +from prompt_toolkit import prompt +from prompt_toolkit.application.current import get_app +from prompt_toolkit.enums import EditingMode +from prompt_toolkit.key_binding import KeyBindings + + +def run(): + # Create a `KeyBindings` that contains the default key bindings. + bindings = KeyBindings() + + # Add an additional key binding for toggling this flag. + @bindings.add("f4") + def _(event): + "Toggle between Emacs and Vi mode." + if event.app.editing_mode == EditingMode.VI: + event.app.editing_mode = EditingMode.EMACS + else: + event.app.editing_mode = EditingMode.VI + + def bottom_toolbar(): + "Display the current input mode." + if get_app().editing_mode == EditingMode.VI: + return " [F4] Vi " + else: + return " [F4] Emacs " + + prompt("> ", key_bindings=bindings, bottom_toolbar=bottom_toolbar) + + +if __name__ == "__main__": + run() diff --git a/examples/prompts/system-clipboard-integration.py b/examples/prompts/system-clipboard-integration.py new file mode 100755 index 0000000..f605921 --- /dev/null +++ b/examples/prompts/system-clipboard-integration.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +""" +Demonstration of a custom clipboard class. +This requires the 'pyperclip' library to be installed. +""" +from prompt_toolkit import prompt +from prompt_toolkit.clipboard.pyperclip import PyperclipClipboard + +if __name__ == "__main__": + print("Emacs shortcuts:") + print(" Press Control-Y to paste from the system clipboard.") + print(" Press Control-Space or Control-@ to enter selection mode.") + print(" Press Control-W to cut to clipboard.") + print("") + + answer = prompt("Give me some input: ", clipboard=PyperclipClipboard()) + print("You said: %s" % answer) diff --git a/examples/prompts/system-prompt.py b/examples/prompts/system-prompt.py new file mode 100755 index 0000000..47aa2a5 --- /dev/null +++ b/examples/prompts/system-prompt.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python +from prompt_toolkit import prompt + +if __name__ == "__main__": + # System prompt. + print( + "(1/3) If you press meta-! or esc-! at the following prompt, you can enter system commands." + ) + answer = prompt("Give me some input: ", enable_system_prompt=True) + print("You said: %s" % answer) + + # Enable suspend. + print("(2/3) If you press Control-Z, the application will suspend.") + answer = prompt("Give me some input: ", enable_suspend=True) + print("You said: %s" % answer) + + # Enable open_in_editor + print("(3/3) If you press Control-X Control-E, the prompt will open in $EDITOR.") + answer = prompt("Give me some input: ", enable_open_in_editor=True) + print("You said: %s" % answer) diff --git a/examples/prompts/terminal-title.py b/examples/prompts/terminal-title.py new file mode 100755 index 0000000..14f9459 --- /dev/null +++ b/examples/prompts/terminal-title.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +from prompt_toolkit import prompt +from prompt_toolkit.shortcuts import set_title + +if __name__ == "__main__": + set_title("This is the terminal title") + answer = prompt("Give me some input: ") + set_title("") + + print("You said: %s" % answer) diff --git a/examples/prompts/up-arrow-partial-string-matching.py b/examples/prompts/up-arrow-partial-string-matching.py new file mode 100755 index 0000000..742a12e --- /dev/null +++ b/examples/prompts/up-arrow-partial-string-matching.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +""" +Simple example of a CLI that demonstrates up-arrow partial string matching. + +When you type some input, it's possible to use the up arrow to filter the +history on the items starting with the given input text. +""" +from prompt_toolkit import PromptSession +from prompt_toolkit.history import InMemoryHistory + + +def main(): + # Create some history first. (Easy for testing.) + history = InMemoryHistory() + history.append_string("import os") + history.append_string('print("hello")') + history.append_string('print("world")') + history.append_string("import path") + + # Print help. + print("This CLI has up-arrow partial string matching enabled.") + print('Type for instance "pri" followed by up-arrow and you') + print('get the last items starting with "pri".') + print("Press Control-C to retry. Control-D to exit.") + print() + + session = PromptSession(history=history, enable_history_search=True) + + while True: + try: + text = session.prompt("Say something: ") + except KeyboardInterrupt: + pass # Ctrl-C pressed. Try again. + else: + break + + print("You said: %s" % text) + + +if __name__ == "__main__": + main() diff --git a/examples/ssh/asyncssh-server.py b/examples/ssh/asyncssh-server.py new file mode 100755 index 0000000..27d0dd2 --- /dev/null +++ b/examples/ssh/asyncssh-server.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python +""" +Example of running a prompt_toolkit application in an asyncssh server. +""" +import asyncio +import logging + +import asyncssh +from pygments.lexers.html import HtmlLexer + +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.contrib.ssh import PromptToolkitSSHServer, PromptToolkitSSHSession +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.shortcuts import ProgressBar, print_formatted_text +from prompt_toolkit.shortcuts.dialogs import input_dialog, yes_no_dialog +from prompt_toolkit.shortcuts.prompt import PromptSession + +animal_completer = WordCompleter( + [ + "alligator", + "ant", + "ape", + "bat", + "bear", + "beaver", + "bee", + "bison", + "butterfly", + "cat", + "chicken", + "crocodile", + "dinosaur", + "dog", + "dolphin", + "dove", + "duck", + "eagle", + "elephant", + "fish", + "goat", + "gorilla", + "kangaroo", + "leopard", + "lion", + "mouse", + "rabbit", + "rat", + "snake", + "spider", + "turkey", + "turtle", + ], + ignore_case=True, +) + + +async def interact(ssh_session: PromptToolkitSSHSession) -> None: + """ + The application interaction. + + This will run automatically in a prompt_toolkit AppSession, which means + that any prompt_toolkit application (dialogs, prompts, etc...) will use the + SSH channel for input and output. + """ + prompt_session = PromptSession() + + # Alias 'print_formatted_text', so that 'print' calls go to the SSH client. + print = print_formatted_text + + print("We will be running a few prompt_toolkit applications through this ") + print("SSH connection.\n") + + # Simple progress bar. + with ProgressBar() as pb: + for i in pb(range(50)): + await asyncio.sleep(0.1) + + # Normal prompt. + text = await prompt_session.prompt_async("(normal prompt) Type something: ") + print("You typed", text) + + # Prompt with auto completion. + text = await prompt_session.prompt_async( + "(autocompletion) Type an animal: ", completer=animal_completer + ) + print("You typed", text) + + # prompt with syntax highlighting. + text = await prompt_session.prompt_async( + "(HTML syntax highlighting) Type something: ", lexer=PygmentsLexer(HtmlLexer) + ) + print("You typed", text) + + # Show yes/no dialog. + await prompt_session.prompt_async("Showing yes/no dialog... [ENTER]") + await yes_no_dialog("Yes/no dialog", "Running over asyncssh").run_async() + + # Show input dialog + await prompt_session.prompt_async("Showing input dialog... [ENTER]") + await input_dialog("Input dialog", "Running over asyncssh").run_async() + + +async def main(port=8222): + # Set up logging. + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + + await asyncssh.create_server( + lambda: PromptToolkitSSHServer(interact), + "", + port, + server_host_keys=["/etc/ssh/ssh_host_ecdsa_key"], + ) + + # Run forever. + await asyncio.Future() + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/telnet/chat-app.py b/examples/telnet/chat-app.py new file mode 100755 index 0000000..2e3508d --- /dev/null +++ b/examples/telnet/chat-app.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +""" +A simple chat application over telnet. +Everyone that connects is asked for his name, and then people can chat with +each other. +""" +import logging +import random +from asyncio import Future, run + +from prompt_toolkit.contrib.telnet.server import TelnetServer +from prompt_toolkit.formatted_text import HTML +from prompt_toolkit.shortcuts import PromptSession, clear + +# Set up logging +logging.basicConfig() +logging.getLogger().setLevel(logging.INFO) + +# List of connections. +_connections = [] +_connection_to_color = {} + + +COLORS = [ + "ansired", + "ansigreen", + "ansiyellow", + "ansiblue", + "ansifuchsia", + "ansiturquoise", + "ansilightgray", + "ansidarkgray", + "ansidarkred", + "ansidarkgreen", + "ansibrown", + "ansidarkblue", + "ansipurple", + "ansiteal", +] + + +async def interact(connection): + write = connection.send + prompt_session = PromptSession() + + # When a client is connected, erase the screen from the client and say + # Hello. + clear() + write("Welcome to our chat application!\n") + write("All connected clients will receive what you say.\n") + + name = await prompt_session.prompt_async(message="Type your name: ") + + # Random color. + color = random.choice(COLORS) + _connection_to_color[connection] = color + + # Send 'connected' message. + _send_to_everyone(connection, name, "(connected)", color) + + # Prompt. + prompt_msg = HTML('[{}] > ').format(color, name) + + _connections.append(connection) + try: + # Set Application. + while True: + try: + result = await prompt_session.prompt_async(message=prompt_msg) + _send_to_everyone(connection, name, result, color) + except KeyboardInterrupt: + pass + except EOFError: + _send_to_everyone(connection, name, "(leaving)", color) + finally: + _connections.remove(connection) + + +def _send_to_everyone(sender_connection, name, message, color): + """ + Send a message to all the clients. + """ + for c in _connections: + if c != sender_connection: + c.send_above_prompt( + [ + ("fg:" + color, "[%s]" % name), + ("", " "), + ("fg:" + color, "%s\n" % message), + ] + ) + + +async def main(): + server = TelnetServer(interact=interact, port=2323) + server.start() + + # Run forever. + await Future() + + +if __name__ == "__main__": + run(main()) diff --git a/examples/telnet/dialog.py b/examples/telnet/dialog.py new file mode 100755 index 0000000..c674a9d --- /dev/null +++ b/examples/telnet/dialog.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +""" +Example of a telnet application that displays a dialog window. +""" +import logging +from asyncio import Future, run + +from prompt_toolkit.contrib.telnet.server import TelnetServer +from prompt_toolkit.shortcuts.dialogs import yes_no_dialog + +# Set up logging +logging.basicConfig() +logging.getLogger().setLevel(logging.INFO) + + +async def interact(connection): + result = await yes_no_dialog( + title="Yes/no dialog demo", text="Press yes or no" + ).run_async() + + connection.send(f"You said: {result}\n") + connection.send("Bye.\n") + + +async def main(): + server = TelnetServer(interact=interact, port=2323) + server.start() + + # Run forever. + await Future() + + +if __name__ == "__main__": + run(main()) diff --git a/examples/telnet/hello-world.py b/examples/telnet/hello-world.py new file mode 100755 index 0000000..c19c60c --- /dev/null +++ b/examples/telnet/hello-world.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +""" +A simple Telnet application that asks for input and responds. + +The interaction function is a prompt_toolkit coroutine. +Also see the `hello-world-asyncio.py` example which uses an asyncio coroutine. +That is probably the preferred way if you only need Python 3 support. +""" +import logging +from asyncio import run + +from prompt_toolkit.contrib.telnet.server import TelnetServer +from prompt_toolkit.shortcuts import PromptSession, clear + +# Set up logging +logging.basicConfig() +logging.getLogger().setLevel(logging.INFO) + + +async def interact(connection): + clear() + connection.send("Welcome!\n") + + # Ask for input. + session = PromptSession() + result = await session.prompt_async(message="Say something: ") + + # Send output. + connection.send(f"You said: {result}\n") + connection.send("Bye.\n") + + +async def main(): + server = TelnetServer(interact=interact, port=2323) + await server.run() + + +if __name__ == "__main__": + run(main()) diff --git a/examples/telnet/toolbar.py b/examples/telnet/toolbar.py new file mode 100755 index 0000000..d6ae886 --- /dev/null +++ b/examples/telnet/toolbar.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python +""" +Example of a telnet application that displays a bottom toolbar and completions +in the prompt. +""" +import logging +from asyncio import run + +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.contrib.telnet.server import TelnetServer +from prompt_toolkit.shortcuts import PromptSession + +# Set up logging +logging.basicConfig() +logging.getLogger().setLevel(logging.INFO) + + +async def interact(connection): + # When a client is connected, erase the screen from the client and say + # Hello. + connection.send("Welcome!\n") + + # Display prompt with bottom toolbar. + animal_completer = WordCompleter(["alligator", "ant"]) + + def get_toolbar(): + return "Bottom toolbar..." + + session = PromptSession() + result = await session.prompt_async( + "Say something: ", bottom_toolbar=get_toolbar, completer=animal_completer + ) + + connection.send(f"You said: {result}\n") + connection.send("Bye.\n") + + +async def main(): + server = TelnetServer(interact=interact, port=2323) + await server.run() + + +if __name__ == "__main__": + run(main()) diff --git a/examples/tutorial/README.md b/examples/tutorial/README.md new file mode 100755 index 0000000..3aa5f70 --- /dev/null +++ b/examples/tutorial/README.md @@ -0,0 +1 @@ +See http://python-prompt-toolkit.readthedocs.io/en/stable/pages/tutorials/repl.html diff --git a/examples/tutorial/sqlite-cli.py b/examples/tutorial/sqlite-cli.py new file mode 100755 index 0000000..ea3e2c8 --- /dev/null +++ b/examples/tutorial/sqlite-cli.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +import sqlite3 +import sys + +from pygments.lexers.sql import SqlLexer + +from prompt_toolkit import PromptSession +from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.lexers import PygmentsLexer +from prompt_toolkit.styles import Style + +sql_completer = WordCompleter( + [ + "abort", + "action", + "add", + "after", + "all", + "alter", + "analyze", + "and", + "as", + "asc", + "attach", + "autoincrement", + "before", + "begin", + "between", + "by", + "cascade", + "case", + "cast", + "check", + "collate", + "column", + "commit", + "conflict", + "constraint", + "create", + "cross", + "current_date", + "current_time", + "current_timestamp", + "database", + "default", + "deferrable", + "deferred", + "delete", + "desc", + "detach", + "distinct", + "drop", + "each", + "else", + "end", + "escape", + "except", + "exclusive", + "exists", + "explain", + "fail", + "for", + "foreign", + "from", + "full", + "glob", + "group", + "having", + "if", + "ignore", + "immediate", + "in", + "index", + "indexed", + "initially", + "inner", + "insert", + "instead", + "intersect", + "into", + "is", + "isnull", + "join", + "key", + "left", + "like", + "limit", + "match", + "natural", + "no", + "not", + "notnull", + "null", + "of", + "offset", + "on", + "or", + "order", + "outer", + "plan", + "pragma", + "primary", + "query", + "raise", + "recursive", + "references", + "regexp", + "reindex", + "release", + "rename", + "replace", + "restrict", + "right", + "rollback", + "row", + "savepoint", + "select", + "set", + "table", + "temp", + "temporary", + "then", + "to", + "transaction", + "trigger", + "union", + "unique", + "update", + "using", + "vacuum", + "values", + "view", + "virtual", + "when", + "where", + "with", + "without", + ], + ignore_case=True, +) + +style = Style.from_dict( + { + "completion-menu.completion": "bg:#008888 #ffffff", + "completion-menu.completion.current": "bg:#00aaaa #000000", + "scrollbar.background": "bg:#88aaaa", + "scrollbar.button": "bg:#222222", + } +) + + +def main(database): + connection = sqlite3.connect(database) + session = PromptSession( + lexer=PygmentsLexer(SqlLexer), completer=sql_completer, style=style + ) + + while True: + try: + text = session.prompt("> ") + except KeyboardInterrupt: + continue # Control-C pressed. Try again. + except EOFError: + break # Control-D pressed. + + with connection: + try: + messages = connection.execute(text) + except Exception as e: + print(repr(e)) + else: + for message in messages: + print(message) + + print("GoodBye!") + + +if __name__ == "__main__": + if len(sys.argv) < 2: + db = ":memory:" + else: + db = sys.argv[1] + + main(db) -- cgit v1.2.3
      ", + "", + "