diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:35:20 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 17:35:20 +0000 |
commit | e106bf94eff07d9a59771d9ccc4406421e18ab64 (patch) | |
tree | edb6545500e39df9c67aa918a6125bffc8ec1aee /examples/prompts | |
parent | Initial commit. (diff) | |
download | prompt-toolkit-upstream.tar.xz prompt-toolkit-upstream.zip |
Adding upstream version 3.0.36.upstream/3.0.36upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
52 files changed, 2297 insertions, 0 deletions
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("<b>Type <u>some input</u>: </b>"), 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..5a30e73 --- /dev/null +++ b/examples/prompts/asyncio-prompt.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +""" +(Python >= 3.6) +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__": + try: + from asyncio import run + except ImportError: + asyncio.run_until_complete(main()) + else: + 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 <ansired>alligator</ansired> is a <u>crocodilian</u> in the genus Alligator of the family Alligatoridae." + ), + "ant": HTML( + "<ansired>Ants</ansired> are eusocial <u>insects</u> of the family Formicidae." + ), + "ape": HTML( + "<ansired>Apes</ansired> (Hominoidea) are a branch of Old World tailless anthropoid catarrhine <u>primates</u>." + ), + "bat": HTML("<ansired>Bats</ansired> are mammals of the order <u>Chiroptera</u>."), + "bee": HTML( + "<ansired>Bees</ansired> are flying <u>insects</u> closely related to wasps and ants." + ), + "beaver": HTML( + "The <ansired>beaver</ansired> (genus Castor) is a large, primarily <u>nocturnal</u>, semiaquatic <u>rodent</u>." + ), + "bear": HTML( + "<ansired>Bears</ansired> are carnivoran <u>mammals</u> of the family Ursidae." + ), + "butterfly": HTML( + "<ansiblue>Butterflies</ansiblue> are <u>insects</u> 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<b>:</b> <ansired>(<" + + family_color + + ">%s</" + + family_color + + ">)</ansired>" + ) % (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..273ebe5 --- /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 Completer, 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..4052929 --- /dev/null +++ b/examples/prompts/auto-completion/fuzzy-custom-completer.py @@ -0,0 +1,57 @@ +#!/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.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=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) <b>This</b> <u>is</u> a <style bg="ansired">toolbar</style>' + ), + ) + 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( + "<username>john</username><at>@</at>" + "<host>localhost</host>" + "<colon>:</colon>" + "<path>/user/john</path>" + '<style bg="#00aa00" fg="#ffffff">#</style> ' + ), + 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..1255d9f --- /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 = list(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( + "<left-part>" + " <username>root</username> " + " abc " + "<path>~/.oh-my-zsh/themes</path>" + "</left-part>" + ) + right_part = HTML( + "<right-part> " + "<branch> master<exclamation-mark>!</exclamation-mark> </branch> " + " <env> py36 </env> " + " <time>%s</time> " + "</right-part>" + ) % (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("<padding>%s</padding>") % (" " * 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("<strong>%s</strong>") % 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..8520790 --- /dev/null +++ b/examples/prompts/inputhook.py @@ -0,0 +1,84 @@ +#!/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.eventloop.defaults import create_event_loop +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..e113330 --- /dev/null +++ b/examples/prompts/placeholder-text.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python +""" +Example of a placeholer 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('<style color="#888888">(please type something)</style>'), + ) + 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<operator1>[a-z]+) \s+ (?P<var1>[0-9.]+) \s+ (?P<var2>[0-9.]+) \s*) | + (\s* (?P<operator2>[a-z]+) \s+ (?P<var1>[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", "<rprompt>"), + ("", " "), + ] + + +def main(): + # Option 1: pass a string to 'rprompt': + answer = prompt("> ", rprompt=" <rprompt> ", style=example_style) + print("You said: %s" % answer) + + # Option 2: pass HTML: + answer = prompt("> ", rprompt=HTML(" <u><rprompt></u> "), style=example_style) + print("You said: %s" % answer) + + # Option 3: pass ANSI: + answer = prompt( + "> ", rprompt=ANSI(" \x1b[4m<rprompt>\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( + [ + "<body>", + "<div>", + "<head>", + "<html>", + "<img>", + "<li>", + "<link>", + "<ol>", + "<p>", + "<span>", + "<table>", + "<td>", + "<th>", + "<tr>", + "<ul>", + ], + 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 <style bg="#222222" fg="#ff8888">[control-t]</style> ' + "to swap between dark/light colors. " + '<style bg="ansiblack" fg="ansiwhite">[%s]</style>' + ) + % on + ) + + text = prompt( + HTML('<style fg="#aaaaaa">Give some animals</style>: '), + 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() |