prompt_toolkit-2.0.10/0000755000175100017510000000000013545410361016312 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/AUTHORS.rst0000644000175100017510000000022413545407022020167 0ustar jonathanjonathan00000000000000Authors ======= Creator ------- Jonathan Slenders Contributors ------------ - Amjith Ramanujam prompt_toolkit-2.0.10/CHANGELOG0000644000175100017510000016571613545410021017535 0ustar jonathanjonathan00000000000000CHANGELOG ========= 2.0.10: 2019-10-03 ------------------ Bug fixes: - Handle HANDLE sizes correctly on windows. This made things break randomly.on 64 bit systems. - Handle terminal size correctly when reported as (0, 0). - Fix width computation in progress bar formatter. - Fix option-up and -down on Mac with iTerm2. - Removed ctrl-c in confirmation prompt. New features: - Added PROMPT_TOOLKIT_NO_CPR=1 environment variable to disable CPR requests. - Accept a pattern in `WordCompleter`. 2.0.9: 2019-02-19 ----------------- Bug fixes: - Fixed `Application.run_system_command` on Windows. - Fixed bug in ANSI text formatting: correctly handle 256/true color sequences. - Fixed bug in WordCompleter. Provide completions when there's a space before the cursor. 2.0.8: 2019-01-27 ----------------- Bug fixes: - Fixes the issue where changes made to the buffer in the accept handler were not reflected in the history. - Fix in the application invalidate handler. This prevents a significat slow down in some applications after some time (especially if there is a refresh interval). - Make `print_container` utility work if the input is not a pty. New features: - Underline non breaking spaces instead of rendering as '&'. - Added mouse support for radio list. - Support completion styles for `READLINE_LIKE` display method. - Accept formatted text in the display text of completions. - Added a `FuzzyCompleter` and `FuzzyWordCompleter`. - Improved error handling in Application (avoid displaying a meaningless AssertionError in many cases). 2.0.7: 2018-10-30 ----------------- Bug fixes: - Fixed assertion in PromptSession: the style_transformation check was wrong. - Removed 'default' attribute in PromptSession. Only ask for it in the `prompt()` method. This fixes the issue that passing `default` once, will store it for all consequent calls in the `PromptSession`. - Ensure that `__pt_formatted_text__` always returns a `FormattedText` instance. This fixes an issue with `print_formatted_text`. New features: - Improved handling of situations where stdin or stdout are not a terminal. (Print warning instead of failing with an assertion.) - Added `print_container` utility. - Sound bell when attempting to edit read-only buffer. - Handle page-down and page-up keys in RadioList. - Accept any `collections.abc.Sequence` for HSplit/VSplit children (instead of lists only). - Improved Vi key bindings: return to navigation mode when Insert is pressed. 2.0.6: 2018-10-12 ----------------- Bug fixes: - Don't use the predefined ANSI colors for colors that are defined as RGB. (Terminals can assign different color schemes for ansi colors, and we don't want use any of those for colors that are defined like #aabbcc for instance.) - Fix in handling of CPRs when patch_stdout is used. Backwards incompatible changes: - Change to the `Buffer` class. Reset the buffer unless the `accept_handler` returns `True` (which means: "keep_text"). This doesn't affect applications that use `PromptSession`. New features: - Added `AdjustBrightnessStyleTransformation`. This is a simple style transformation that improves the rendering on terminals with light or dark background. - Improved performance (string width caching and line height calculation). - Improved `TextArea`: * Exposed `focus_on_click`. * Added attributes: `auto_suggest`, `complete_while_typing`, `history`, `get_line_prefix`, `input_processors`. * Made attributes writable: `lexer`, `completer`, `complete_while_typing`, `accept_handler`, `read_only`, `wrap_lines`. 2.0.5: 2018-09-30 ----------------- Bug fixes: - Fix in `DynamicContainer`. Return correct result for `get_children`. This fixes a bug related to focusing. - Properly compute length of `start`, `end` and `sym_b` characters of progress bar. - CPR (cursor position request) fix. Backwards incompatible changes: - Stop restoring `PromptSession` attributes when exiting prompt. New features: - Added `get_line_prefix` attribute to window. This opens many possibilities: * Line wrapping (soft and hard) can insert whitespace in front of the line, or insert some symbols in front. Like the Vim "breakindent" option. * Single line prompts also support line continuations now. * Line continuations can have a variable width. - For VI mode: implemented temporary normal mode (control-O in insert mode). - Added style transformations API. Useful for swapping between light and dark color schemes. Added `swap_light_and_dark_colors` parameter to `prompt()` function. - Added `format()` method to ANSI formatted text. - Set cursor position for Button widgets. - Added `pre_run` argument to `PromptSession.prompt()` method. 2.0.4: 2018-07-22 ----------------- Bug fixes: - Fix render height for rendering full screen applications in Windows. - Fix in `TextArea`. Set `accept_handler` to `None` if not given. - Go to the beginning of the next line when enter is pressed in Vi navigation mode, and the buffer doesn't have an accept handler. - Fix the `default` argument of the `prompt` function when called multiple times. - Display decomposed multiwidth characters correctly. - Accept `history` in `prompt()` function again. Backwards incompatible changes: - Renamed `PipeInput` to `PosixPipeInput`. Added `Win32PipeInput` and `create_input_pipe`. - Pass `buffer` argument to the `accept_handler` of `TextArea`. New features: - Added `accept_default` argument to `prompt()`. - Make it easier to change the body/title of a Frame/Dialog. - Added `DynamicContainer`. - Added `merge_completers` for merging multiple completers together. - Add vt100 data to key presses in Windows. - Handle left/right key bindings in Vi block insert mode. 2.0.3: 2018-06-08 ----------------- Bug fixes: - Fix in 'x' and 'X' Vi key bindings. Correctly handle line endings and args. - Fixed off by one error in Vi line selection. - Fixed bugs in Vi block selection. Correctly handle lines that the selection doesn't cross. - Python 2 bugfix. Handle str/unicode correctly. - Handle option+left/right in iTerm. 2.0.2: 2018-06-03 ----------------- Bug fixes: - Python 3.7 support: correctly handle StopIteration in asynchronous generator. - Fixed off-by-one bug in Vi visual block mode. - Bugfix in TabsProcessor: handle situations when the cursor is at the end of the line. 2.0.1: 2018-06-02 ----------------- Version 2.0 includes a big refactoring of the internal architecture. This includes the merge of the CommandLineInterface and the Application object, a rewrite of how user controls are focused, a rewrite of how event loops work and the removal of the buffers dictionary. This introduces many backwards incompatible changes, but the result is a very nice and powerful architecture. Most architectural changes effect full screen applications. For applications that use `prompt_toolkit.shortcuts` for simple prompts, there are fewer incompatibilities. Changes: - No automatic translation from \r into \n during the input processing. These are two different keys that can be handled independently. This is a big backward-incompatibility, because the `Enter` key is `ControlM`, not `ControlJ`. So, now that we stopped translating \r into \n, it could be that custom key bindings for `Enter` don't work anymore. Make sure to bind `Keys.Enter` instead of `Keys.ControlJ` for handling the `Enter` key. - The `CommandLineInterface` and the `Application` classes are merged. First, `CommandLineInterface` contained all the I/O objects (like the input, output and event loop), while the `Application` contained everything else. There was no practical reason to keep this separation. (`CommandLineInterface` was mostly a proxy to `Application`.) A consequence is that almost all code which used to receive a `CommandLineInterface`, will now use an `Application`. Usually, where we had an attribute `cli`, we'll now have an attribute `app`. Secondly, the `Application` object is no longer passed around. The `get_app` function can be used at any time to acquire the active application. (For backwards-compatibility, we have aliases to the old names, whenever possible.) - prompt_toolkit no longer depends on Pygments, but it can still use Pygments for its color schemes and lexers. In many places we used Pygments "Tokens", this has been replaced by the concept of class names, somewhat similar to HTML and CSS. * `PygmentsStyle` and `PygmentsLexer` adaptors are available for plugging in Pygments styles and lexers. * Wherever we had a list of `(Token, text)` tuples, we now have lists of `(style_string, text)` tuples. The style string can contain both inline styling as well as refer to a class from the style sheet. `PygmentsTokens` is an adaptor that converts a list of Pygments tokens into a list of `(style_string, text)` tuples. - Changes in the `Style` classes. * `style.from_dict` does not exist anymore. Instantiate the ``Style`` class directory to create a new style. ``Style.from_dict`` can be used to create a style from a dictionary, where the dictionary keys are a space separated list of class names, and the values, style strings (like before). * `print_tokens` was renamed to `print_formatted_text`. * In many places in the layout, we accept a parameter named `style`. All the styles from the layout hierarchy are combined to decide what style to be used. * The ANSI color names were confusing and inconsistent with common naming conventions. This has been fixed, but aliases for the original names were kept. - The way focusing works is different. Before it was always a `Buffer` that was focused, and because of that, any visible `BufferControl` that contained this `Buffer` would be focused. Now, any user control can be focused. All of this is handled in the `Application.layout` object. - The `buffers` dictionary (`CommandLineInterface.buffers`) does not exist anymore. Further, `buffers` was a `BufferMapping` that keeps track of which buffer has the focus. This significantly reduces the freedom for creating complex applications. We wanted to move toward a layout that can be defined as a (hierarchical) collection of user widgets. A user widget does not need to have a `Buffer` underneath and any widget should be focusable. * `layout.Layout` was introduced to contain the root layout widget and keep track of the focus. - The key bindings were refactored. It became much more flexible to combine sets of key bindings. * `Registry` has been renamed to `KeyBindings`. * The `add_binding` function has been renamed to simply `add`. * Every `load_*` function returns one `KeyBindings` objects, instead of populating an existing one, like before. * `ConditionalKeyBindings` was added. This can be used to enable/disable all the key bindings from a given `Registry`. * A function named `merge_key_bindings` was added. This takes a list of `KeyBindings` and merges them into one. * `key_binding.defaults.load_key_bindings` was added to load all the key bindings. * `KeyBindingManager` has been removed completely. * `input_processor` was renamed to `key_processor`. Further: * The `Key` class does not exist anymore. Every key is a string and it's considered fine to use string literals in the key bindings. This is more readable, but we still have run-time validation. The `Keys` enum still exist (for backwards-compatibility, but also to have an overview of which keys are supported.) * 'enter' and 'tab' are key aliases for 'c-m' and 'c-i'. - User controls can define key bindings, which are active when the user control is focused. * `UIControl` got a `get_key_bindings` (abstract) method. - Changes in the layout engine: * `LayoutDimension` was renamed to `Dimension`. * `VSplit` and `HSplit` now take a `padding` argument. * `VSplit` and `HSplit` now take an `align` argument. (TOP/CENTER/BOTTOM/JUSTIFY) or (LEFT/CENTER/RIGHT/JUSTIFY). * `Float` now takes `allow_cover_cursor` and `attach_to_window` arguments. * `Window` got an `WindowAlign` argument. This can be used for the alignment of the content. `TokenListControl` (renamed to `FormattedTextControl`) does not have an alignment argument anymore. * All container objects, like `Window`, got a `style` argument. The style for parent containers propagate to child containers, but can be overriden. This is in particular useful for setting a background color. * `FillControl` does not exist anymore. Use the `style` and `char` arguments of the `Window` class instead. * `DummyControl` was added. * The continuation function of `PromptMargin` now takes `line_number` and `is_soft_wrap` as input. - Changes to `BufferControl`: * The `InputProcessor` class has been refactored. The `apply_transformation` method should now takes a `TransformationInput` object as input. * The text `(reverse-i-search)` is now displayed through a processor. (See the `shortcuts` module for an example of its usage.) - `widgets` and `dialogs` modules: * A small collection of widgets was added. These are more complex collections of user controls that are ready to embed in a layout. A `shortcuts.dialogs` module was added as a high level API for displaying input, confirmation and message dialogs. * Every class that exposes a ``__pt_container__`` method (which is supposed to return a ``Container`` instance) is considered a widget. The ``to_container`` shortcut will call this method in situations where a ``Container`` object is expected. This avoids inheritance from other ``Container`` types, but also having to unpack the container object from the widget, in case we would have used composition. * Warning: The API of the widgets module is not considered stable yet, and can change is the future, if needed. - Changes to `Buffer`: * A `Buffer` no longer takes an `accept_action`. Both `AcceptAction` and `AbortAction` have been removed. Instead it takes an `accept_handler`. - Changes regarding auto completion: * The left and right arrows now work in the multi-column auto completion menu. * By default, autocompletion is synchronous. The completer needs to be wrapped in `ThreadedCompleter` in order to get asynchronous autocompletion. * When the completer runs in a background thread, completions will be displayed as soon as they are generated. This means that we don't have to wait for all the completions to be generated, before displaying the first one. The completion menus are updated as soon as new completions arrive. - Changes regarding input validation: * Added the `Validator.from_callable` class method for easy creation of new validators. - Changes regarding the `History` classes: * The `History` base class has a different interface. This was needed for asynchronous loading of the history. `ThreadedHistory` was added for this. - Changes related to `shortcuts.prompt`: * There is now a class `PromptSession` which also has a method `prompt`. Both the class and the method take about the same arguments. This can be used to create a session. Every `prompt` call of the same instance will reuse all the arguments given to the class itself. The input history is always shared during the entire session. Of course, it's still possible to call the global `prompt` function. This will create a new `PromptSession` every time when it's called. * The `prompt` function now takes a `key_bindings` argument instead of `key_bindings_registry`. This should only contain the additional bindings. (The default bindings are always included.) - Changes to the event loops: * The event loop API is now closer to how asyncio works. A prompt_toolkit `Application` now has a `Future` object. Calling the `.run_async()` method creates and returns that `Future`. An event loop has a `run_until_complete` method that takes a future and runs the event loop until the Future is set. The idea is to be able to transition easily to asyncio when Python 2 support can be dropped in the future. * `Application` still has a method `run()` that underneath still runs the event loop until the `Future` is set and returns that result. * The asyncio adaptors (like the asyncio event loop integration) now require Python 3.5. (We use the async/await syntax internally.) * The `Input` and `Output` classes have some changes. (Not really important.) * `Application.run_sub_applications` has been removed. The alternative is to call `run_coroutine_in_terminal` which returns a `Future`. - Changes to the `filters` module: * The `Application` is no longer passed around, so both `CLIFilter` and `SimpleFilter` were merged into `Filter`. `to_cli_filter` and `to_simple_filter` became `to_filter`. * All filters have been turned into functions. For instance, `IsDone` became `is_done` and `HasCompletions` became `has_completions`. This was done because almost all classes were called without any arguments in the `__init__` causing additional braces everywhere. This means that `HasCompletions()` has to be replaced by `has_completions` (without parenthesis). The few filters that took arguments as input, became functions, but still have to be called with the given arguments. For new filters, it is recommended to use the `@Condition` decorator, rather then inheriting from `Filter`. - Other renames: * `IncrementalSearchDirection` was renamed to `SearchDirection`. * The `use_alternate_screen` parameter has been renamed to `full_screen`. * `Buffer.initial_document` was renamed to `Buffer.document`. * `TokenListControl` has been renamed to `FormattedTextControl`. * `Application.set_return_value` has been renamed to `Application.set_result`. - Other new features: * `DummyAutoSuggest` and `DynamicAutoSuggest` were added. * `DummyClipboard` and `DynamicClipboard` were added. * `DummyCompleter` and `DynamicCompleter` were added. * `DummyHistory` and `DynamicHistory` was added. * `to_container` and `to_window` utilities were added. 1.0.9: 2016-11-07 ----------------- Fixes: - Fixed a bug in the `cooked_mode` context manager. This caused a bug in ptpython where executing `input()` would display ^M instead of accepting the input. - Handle race condition in eventloop/posix.py - Updated ANSI color names for vt100. (High and low intensity colors were swapped.) New features: - Added yank-nth-arg and yank-last-arg readline commands + Emacs bindings. - Allow searching in Vi selection mode. - Made text objects of the Vi 'n' and 'N' search bindings. This adds for instance the following bindings: cn, cN, dn, dN, yn, yN 1.0.8: 2016-10-16 ----------------- Fixes: - In 'shortcuts': complete_while_typing was a SimpleFilter, not a CLIFilter. - Always reset color attributes after rendering. - Handle bug in Windows when '$TERM' is not defined. - Ignore errors when calling tcgetattr/tcsetattr. (This handles the "Inappropriate ioctl for device" crash in some scenarios.) - Fix for Windows. Correctly recognize all Chinese and Lithuanian characters. New features: - Added shift+left/up/down/right keys. - Small performance optimization in the renderer. - Small optimization in the posix event loop. Don't call time.time() if we don't have an inputhook. (Less syscalls.) - Turned the _max_postpone_until argument of call_from_executor into a float. (As returned by `time.time`.) This will do less system calls. It's backwards-incompatible, but this is still a private API, used only by pymux.) - Added Shift-I/A commands in Vi block selection mode for inserting text at the beginning of each line of the block. - Refactoring of the 'selectors' module for the posix event loop. (Reuse the same selector object in one loop, don't recreate it for each select.) 1.0.7: 2016-08-21 ----------------- Fixes: - Bugfix in completion. When calculating the common completion to be inserted, the new completions were calculated wrong. - On Windows, avoid extra vertical scrolling if the cursor is already on screen. New features: - Support negative arguments for next/previous word ending/beginning. 1.0.6: 2016-08-15 ----------------- Fixes: - Go to the start of the line in Vi navigation mode, when 'j' or 'k' have been pressed to navigate to a new history entry. - Don't crash when pasting text that contains \r\n characters. (This could happen in iTerm2.) - Python 2.6 compatibility fix. - Allow pressing before each -ve argument. - Better support for conversion from #ffffff values to ANSI colors in Vt100_Output. * Prefer colors with some saturation, instead of gray colors, if the given color was not gray. * Prefer a different foreground and background color if they were originally not the same. (This avoids concealing text.) New features: - Improved ANSI color support. * If the $PROMPT_TOOLKIT_ANSI_COLORS_ONLY environment variable has been set, use the 16 ANSI colors only. * Take an `ansi_colors_only` parameter in `Vt100_Output` and `shortcuts.create_output`. 1.0.5: 2016-08-04 ----------------- Fixes: - Critical fix for running on Windows. The gevent work-around in the inputhook caused 'An operation was attempted on something that is not a socket'. 1.0.4: 2016-08-03 ----------------- Fixes: - Key binding fixes: * Improved handling of repeat arguments in Emacs mode. Pressing sequences like 'esc---123' do now work (like GNU Readline): - repetition of the minus sign is ignored. - No esc prefix is required for each digit. * Fix in ControlX-ControlX binding. * Fix in bracketed paste. * Pressing Control-U at the start of the line now deletes the newline. * Pressing Control-K at the end of the line, deletes the newline after the cursor. * Support negative argument for Control-K * Fixed cash when left/right were pressed with a negative argument. (In Emacs mode.) * Fix in ControlUp/ControlDown key bindings. * Distinguish backspace from Control-H. They are not the same. * Delete in front of the cursor when a negative argument has been given to backspace. * Handle arrow keys correctly in emacs-term. - Performance optimizations: * Performance optimization in Registry. * Several performance optimization in filters. * Import asyncio inline (only if required). - Use the best possible selector in the event loop. This fixes bugs in situations where we have too many open file descriptors. - Fix UI freeze when gevent monkey patch has been applied. - Fix segmentation fault in Alpine Linux. (Regarding the use of ioctl.) - Use the correct colors on Windows. (When the foreground/background colors have been modified.) - Display a better error message when running in Idle. - Additional flags for vt100 inputs: disable flow control. - Also patch stderr in CommandLineInterface.patch_stdout_context. New features: - Allow users to enter Vi digraphs in reverse order. - Improved autocompletion behaviour. See IPython issue #9658. - Added a 'clear' function in the shortcuts module. For future compatibility: - `Keys.Enter` has been added. This is the key that should be bound for handling the enter key. Right now, prompt_toolkit translates \r into \n during the handling of the input; this is not correct and makes it impossible to distinguish between ControlJ and ControlM. Some applications bind ControlJ for custom handling of the enter key, because this equals \n. However, in a future version we will stop replacing \r by \n and at that point, the enter key will be ControlM. So better is to use `Keys.Enter`, which becomes an alias for whatever the enter key translates into. 1.0.3: 2016-06-20 ----------------- Fixes: - Bugfix for Python2 in readline-like completion. - Bugfix in readline-like completion visualisation. New features: - Added `erase_when_done` parameter to the `Application` class. (This was required for the bug fixes.) - Added (experimental) `CommandLineInterface.run_application_generator` method. (Also required for the bug fix.) 1.0.2: 2016-06-16 ----------------- Fixes: - Don't select the first completion when `complete_while_typing` is False. (Restore the old behaviour.) 1.0.1: 2016-06-15 ----------------- Fixes: - Bugfix in GrammarValidator and SentenceValidator. - Don't leave the alternate screen on resize events. - Use errors=surrogateescape, in order to handle mouse events in some terminals. - Ignore key presses in _InterfaceEventLoopCallbacks.feed_key when the CLI is in the done state. - Bugfix in get_common_complete_suffix. Don't return any suffix when there are completions that change whatever is before the cursor. - Bugfix for Win32/Python2: use unicode literals: This crashed arrow navigation on Windows. - Bugfix in InputProcessor: handling of more complex key bindings. - Fix: don't apply completions, if there is only one completion which doesn't have any effect. - Fix: correctly handle prompts starting with a newline in prompt_toolkit.shortcuts. - Fix: thread safety in autocomplete code. - Improve styling for matching brackets. (Allow individual styling for the bracket under the cursor and the other.) - Fix in ShowLeadingWhiteSpaceProcessor/ShowTrailingWhiteSpaceProcessor: take output encoding into account. (The signature had to change a little for this.) - Bug fix in key bindings: only activate Emacs system/open-in-editor bindings if editing_mode is emacs. - Added write_binary parameter to Vt100_Output. This fixes a bug in some cases where we expect it to write non-encoded strings. - Fix key bindings for Vi mode registers. New features (**): - Added shortcuts.confirm/create_confirm_application function. - Emulate bracketed paste on Windows. (When the input stream contains multiple key presses among which a newline and at least one other character, consider this a paste event, and handle as bracketed paste on Unix. - Added key handler for displaying completions, just like readline does. - Implemented Vi guu,gUU,g~~ key bindings. - Implemented Vi 'gJ' key binding. - Implemented Vi ab,ib,aB,iB text objects. - Support for ZeroWidthEscape tokens in prompt and token lists. Used to support final shell integration. - Fix: Make document.text/cursor_position/selection read-only. (Changing these would break the caching causing bigger issues.) - Using pytest for unit tests. - Allow key bindings to have Keys.Any at any possible position. (Not just the end.) This made it significantly easier to write the named register Vi bindings, resulting in an approved start-up time.) - Better feedback when entering multi-key key bindings in insert mode. (E.g. when 'jj' would be mapped to escape.) - Small improvement in key processor: allow key bindings to generate new key presses. - Handle ControlUp and ControlDown by default: move to the previous/next record in the history. - Accept 'char'/'get_char' parameters in FillControl. - Added refresh_interval method to prompt() function. Performance improvements: - Improve the performance of test_callable_args: this should significantly increase the start-up time. - Start-up time for creating the Vi bindings has been improved significantly. (**) Some small backwards-compatible features were allowed for this minor release. After evaluating the impact/risk/work involved we concluded that we could ship these in a minor release. 1.0.0: 2016-05-05 ----------------- Fixes: - Adjust minimum completion menu width to match UIControl and Window class. - Bugfix regarding weakref in InputProcessor. - Fix for pypy3: bug in WeakValueDictionary. - Correctly handle '0' key binding in Vi mode. - Also load Vi bindings by default in Application if no registry has been given. - Only go into selection mode if the current buffer is not empty. - Close PipeInput after usage. - Only use 16 colors in (Emacs) eterm-color. - Bugfix in "xP Vi key binding. - Bugfix in Vi { and } key binding. - Fix: use correct token for Scrollbar in MultiColumnCompletionMenuControl. - Handle negative values in translate_row_col_to_index. - Handle decomposed unicode characters. - Fixed Window.always_hide_cursor. (Parameter was ignored.) - Fix in zz Vi key binding. (When render info is not available.) - Fix in Document.get_cursor_up_position. (When an argument is given.) New features: - Separated `load_mouse_bindings`. - Refactoring/simplification of the key bindings: better use of filters and CLI.editing_mode. - Added DummyOutput class and a few unit tests that test the whole CLI. - Use the bisect module in Document._line_start_indexes instead of a custom binary search. This should improve the performance. - Stay in the same column when doing multiple up/down movements. - Visual improvements: * Implemented cursorcolumn, cursorline and colorcolumn. * Only reserve menu space when `complete_while_typing=True` or when there are completions to be displayed. * Support for chaining tokens for combined styles. SelectedText will now reverse the colors from the highlighting by default. Style `Token.SelectedText` to set a fixed foreground/background. Also for SearchMatch, we now use combined tokens. * Support for dark gray on Windows. * Default token for SystemToolbar and SearchToolbar. * Display selection also on empty lines. - Emacs key bindings improved: * Recognize + handle ControlDelete key. * Implemented meta-* and control-backslash key bindings. - Vi key bindings improved: * Handle inclusive and linewise motions properly. * Fix g_ motion off by one character, and don't work when cursor is in the trailing whitespace part of line. * Make a(/a)/i(/i)/... motions. Find enclosing brackets instead of the next bracket. * Update N% motion according to vim behaviors. * Fix | motion off by one character. * ge/gE motions go to end of previous word, not start. * Added Vi 'gm' key binding. * Implemented 'gq' key binding in Vi mode. (Reshape text.) * Vi operator/text object separation for key bindings. * Added 'ap' (auto-paragraph) text object. * Implemented Vi digraphs. ControlK will now insert a digraph. * Implemented vi tilde_operator. * Support named registers. * Vi < and > key bindings became operators. * Text objects and motions are now separate bindings. * Improved copy/paste in Vi mode. Backwards-incompatible changes: - Don't reset the current buffer anymore by default in CommandLineInterface.run(). Passing `reset_current_buffer=True` is now required. - Renamed MouseEventTypes to MouseEventType for consistency. The old name is still valid, but deprecated. - Refactoring of Callbacks. All events should now receive one argument, which is the sender. (Further, Callback was renamed to Event.) This is mostly used internally. - Moved on_invalidate callback from CommandLineInterface to Application - Renamed `PipeInput.send` to `PipeInput.send_text`. (Old deprecated name is still kept as a valid alias.) - Renamed SimpleLexer.default_token to SimpleLexer.token. (+ backwards-compatibility.) - Refactoring of the filters: `ViStateFilter` has been deprecated. (Should not be used anymore.) Use the filters, as defined in prompt_toolkit.filters. - `editing_mode` is now a property of `CommandLineInterface`. This is replacing the `vi_mode` parameter in `KeyBindingManager`. - The default accept_action for the default Buffer in Application now becomes IGNORE. This is a much more sensible default. Pass RETURN_DOCUMENT to get the previous behaviour, - Always expect an EventLoop instance in CommandLineInterface. Creating it in __init__ caused a memory leak. 0.60: 2016-03-14 ---------------- Fixes: - Fix in Document.paste. (The screen was not updated after an undo of a paste.) - Don't use deprecated inspect.getargspec on Python 3. - Fixed reading input on Windows when input was piped in stdin. - Use correct file descriptors for input/output in run_system_command. - Always correctly split prompt in shortcuts.prompt. (Even when multiline=False) - Correctly align right prompt to the top when the left prompt consists of multiple lines. - Correctly use Token.Transparent as default token for a TokenListControl. - Fix in syntax synchronisation. (Better handle the case when no synchronisation point was found.) - Send SIGTSTP to the whole process group. - Correctly raise on_buffer_changed on all text changes. - Fix in regular_languages.GrammarLexer. (Fixes bug in ptipython syntax highlighting.) New features: - Add support for additional readers to the Win32 event loop. - Added on_render event. - Carry the weight in layout dimensions to allow stretching. 0.59: 2016-02-27 ---------------- Fixes: - Set correct default color on Windows. (Gray instead of high intensity gray.) - Reverse colors on Windows when foreground/background color have not been specified. - Correct handling of mouse events for FillControl. - Take margin into account when calculating Window height. (Fixes bug in multiline prompt.) - Handle division by zero in UIContent.get_height_for_text. 0.58: 2016-02-23 ---------------- Fixes: - Correctly return result for mouse handler in TokenListControl. - Bugfix in meta-backspace key binding. (Delete all whitespace before the cursor, when there is only whitespace.) - Bugfix in Vi gu, gU, g? and g~ key bindings (in selection mode). - Correctly restore default console attributes on Windows. - Disable bracketed paste support in ConEmu. (This was broken.) - When an unknown exception is raised in `CommandLineInterface.run()`, don't forget to redraw the CLI. New features: - Many performance improvements and better caching. (Especially in the `Document` class.) - Support for continuation tokens in `shortcuts.prompt` and `shortcuts.create_prompt_layout`. - Added `shortcuts.print_tokens` function for printing colored output. - Sound bell when nothing was deleted. - Added escape sequences for F1-F5 keys on the Linux console. - Improved support for the Linux console. (Switch back to 16 colors.) - Added F13-F24 input codes for xterm. - Created prompt_toolkit.token. A custom Token implementation, that is compatible with Pygments.token. (This way, Pygments becomes an optional dependency. For many use cases, nothing except the Token class from Pygments was used, so it was a bit overkill to install Pygments for only that.) - Refactoring of prompt_toolkit.styles. - `Float` objects got a `hide_when_covering_content` option. - Implementation of RPROMPT, like ZSH: Added `get_rprompt_tokens` to `create_prompt_layout`. - Some improvements to the default style. - Also handle Ctrl-R and Ctrl-S in Vi mode when searching. - Added TabsProcessor: a tool to visualise tabs instead of displaying ^I. - Give a better error message when trying to run in git-bash. - Support for ANSI color names in style dictionaries. - Big refactoring of the `Window` and `UIControl` classes. This should result in huge performance improvements on big inputs. (While first, a document could have 1,000 lines; now it can have about 100,000 lines on the same system.) The Window and UIControl have been rewritten very much. Rather than each time rendering the whole user control, we now only have to render the visible part. Because of this, many pieces had to be rewritten: - UIControls work differently. They return a `UIContent` instance that consist of a collection of lines. - All processors have been rewritten. (Their API changed as well, because they process one line at a time.) - Lexers work differently. `Lexer.lex_document` should now return a function that returns the tokens for one line. PygmentsLexer has been optimized that it becomes 'lazy', and it has optional syntax synchronisation. That means, that the lexer doesn't have to start the lexing at the beginning of the document. (Which would be slow for big documents.) Backwards-incompatible changes: - As mentioned above, the refactoring of `Window` and `UIControl` caused many "internal" APIs to change. All custom `UIControl`, `Processor` and `Lexer` classes have to be rewritten. However, for most applications this should not be an issue. Especially, the `shortcuts.prompt` function is backwards-compatible. - `wrap_lines` became a property of `Window` instead of `BufferControl`. 0.57: 2016-01-04 ---------------- Fixes: - Made `max_render_postpone_time` configurable. The current default was bad. (We should probably always draw the UI once every cycle of the event loop.) 0.56: 2016-01-03 ---------------- Fixes: - Fix in bracketed paste. It was not correctly enabled for each prompt. 0.55: 2016-01-03 ---------------- New features: - Implemented bracketed paste mode. (This allows much faster pasting, as well as pasting without going into paste mode. This makes sure that indentation in ptpython for instance is kept correctly.) - Added support for italic output and blink. (For terminals that support it.) - Added get_horizontal_scroll, get_vertical_scroll and always_hide_cursor parameters to Window. - Refactoring of the posix event loop. Better scheduling of all tasks/FDs to avoid starvation. (Everything should feel more responsive in high CPU situations.) - Added get_default_char function to TokenListControl. - AppendAutoSuggestion now accepts a token parameter. - Support for ansi color names in styles. - Accept get_width/get_height parameters in Float. - Added Output.write_raw and accept 'raw' parameter in CommandLineInterface.stdout_proxy. - Better caching of tokens in TokenListControl. - Add mouse support to TokenListControl. - Display "Window too small" when the window becomes too small. - Added 'bell' function to Output. - Accept weights in HSplit/VSplit. - Added Registry.remove_binding method to dynamically remove key bindings. - Added focus_on_click parameter to BufferControl. - Introduced BufferMapping class as a wrapper around the buffers dictionary. This one also contains the focus stack. - Improved 'v' and 'V' key bindings. Allow switching between line and character selection modes. - Added layout.highlighters. A new, much faster way to do selection and search highlighting. - Make search_state dynamic for key bindings. - Added 'sentence' option to WordCompleter. - Cache Document.lines for better performance. - Implementation of BLOCK selections. (Cut, copy, paste.) - Accept a 'reserve_space_for_menu' parameter in the shortcuts. (This is an integer.) - Support for 24bit true color on vt100 terminals. - Added CommandLineInterface.on_invalidate event. - Added __version__ to __init__.py. Fixes: - Always show cursor in the 'done' state. - Allow HSplit to have zero children. - Bugfix for handling of backslash on Windows with some non-us keyboards. (Ptpython issue #28.) - Never render characters outside the visible screen region. - Fix in WordCompleter. When case insensitive and input contained uppercase. - Highlight search match when the cursor is at any position on the match. (not just the beginning.) Backwards-incompatible changes: (Most changes will probably not have an impact on external applications.) - Change in the `Style` API. This allows caching of Attrs in renderer and faster rendering. (Style now has a get_attrs_for_token instead of a get_token_to_attributes_dict method.) - Removed DefaultStyle. Created PygmentsStyle.from_defaults class method instead. - Removed AbortAction.IGNORE. This was ambiguous. - Accept 'cli' parameter in 'walk' and 'find_window_for_buffer_name'. - The focus stack is now stored in BufferMapping. - ViStateFilter and KeyBindingManager now accept a get_vi_state callable instead of vi_state itself. (This way a key bindings registry becomes stateless.) - HighlightSearchProcessor and HighlightSelectionProcessor became deprecated. (Use highlighters instead.) 0.54: 2015-10-29 ---------------- New features: - Allow CommandLineInterface to run in any thread. - Hide cursor while rendering. - Added add_reader/remove_reader methods to EventLoop. - Support for 'reverse' style. - Redraw more lazy, by using invalidate. - Added show_cursor property to Screen. - Center or right align text in TokenListControl also when it spans multiple lines. Fixes: - Bugfix in PathCompleter. (Expanduser issue.) - Fix in signal handler. - Use utf-8 encoding in Vt100_Output by default. - Use correct default token in BufferControl. - Fix in ControlL key binding. Use @handle to allow deactivation. Backwards-incompatible changes: - Renamed create_default_layout to create_prompt_layout - Renamed create_default_application to create_prompt_application - Renamed Layout to Container. - Renamed CommandLineInterfaces.request_redraw to invalidate. - Changed the actual value of SEARCH_BUFFER, DEFAULT_BUFFER, SYSTEM_BUFFER and DUMMY_BUFFER. - Changed order of keyword arguments of the BufferControl class. "buffer_name" now comes first. - Removed old pt(i)python code. 0.53: 2015-10-06 ---------------- New features: - Handling of the insert key in Vi mode. - Added 'zt' and 'zb' Vi key bindings. - Added delete key binding for deleting selected text. - Select word below cursor on double mouse click. - Added `wrap_lines` option to TokenListControl. - Added `KeyBindingManager.for_prompt`. Fixes: - Fix in rendering output. - Reset renderer correctly in run_in_terminal. - Only reset buffer when using `AbortAction.RETRY`. - Fix in handling of exit (Ctrl-D) key presses. - Fix in `CompleteEvent`. Correctly set `completion_requested`. Backwards-incompatible changes: - Renamed `ValidationError.index` to `ValidationError.cursor_position`. - Renamed `shortcuts.get_input` to `shortcuts.prompt`. - Return empty string instead of None in `Document.current_char`/`char_before_cursor`. 0.52: 2015-09-24 ---------------- Fixes: - Fix in auto suggestion: hide suggestion when accepting input. 0.51: 2015-09-24 ---------------- New features: - Mouse support. (Scrolling and clicking for vt100 terminals. For Windows only clicking.) Both the autocompletion menus and buffer controls respond to scrolling and clicking. - Added auto suggestions. (Like the fish shell.) - Stdout proxy become thread safe. - Linewrapping can now be disabled, instead we get horizontal scrolling. - Line numbering can now be relative. Like the vi 'relativenumber' option. Fixes: - Fixed excessive scrolling in Windows. - Bugfix in search highlighting. - Copy all words during repetition of Ctrl-W presses. - The 'libs' folder has been removed. - Fix in MultiColumnCompletionsMenu: don't create very big columns. Backwards-incompatible changes: - Disable search by default in KeyBindingManager. - Separated abort/exit key bindings. Disabled by default in KeyBindingManager. - 'Ignore' became the default on_abort action in `Application`. - 'Ignore' became the default accept_action in `Buffer`. - The layout processors have been refactored. The API is changed. - `SwitchableValidator` has been renamed to `ConditionalValidator`. - `WindowRenderInfo` has several incompatible changes. - Margins have been refactored completely. Now it's the window that has the margin instead of `BufferControl`. Is is both much more performant and flexible. 0.50: 2015-09-06 ---------------- Fix: - Leaving of alternate screen on Windows. 0.49: 2015-09-06 ---------------- New features: - Added MANIFEST.in - Better support for multiline prompts in shortcuts. - Added Document.set_document method. - Added 'default' argument to `shortcuts.create_default_application`. - Added `align_center` option for `TokenListControl`. - Added optional key bindings for full page navigation. (Moved key bindings from pyvim into prompt-toolkit.) - Accepts default_char in BufferControl for filling the background. - Added InFocusStack filter. Fixes: - Small fix in TokenListControl: use the right Char for aligning. Backwards-incompatible changes: - Removed deprecated 'tokens' attribute from GrammarLexer. 0.48: 2015-09-02 ---------------- New features: - run_in_terminal now returns the result of the called function. - Made history attribute of Buffer class public. - Added support for sub CommandLineInterfaces. - Accept optional vi_state parameter in KeyBindingManager. Fixes: - Pop-up menu positioning. The menu was shown too often above instead of below the cursor. - Fix in Control-W key binding. When there is only whitespace before the cursor, delete the whitespace. - Rendering bug fix in open_in_editor: run editor using cli.run_in_terminal. - Fix in renderer. Correctly reserve the vertical space as required by the layout. - Small fix in Margin ABC. - Added __iter__ to History ABC. - Small bugfix in CommandLineInterface: create correct eventloop when no eventloop was given. - Never schedule a second repaint operation when a previous was not yet executed. 0.47: 2015-08-19 ---------------- New features: - Added `prompt_toolkit.layout.utils.iter_token_lines`. - Allow `None` values on the focus stack. - Buffers can be readonly. Added `IsReadOnly` filter. - `eager` behaviour for key bindings. When a key binding is eager it will be executed as soon as it's matched, even when there is another binding that starts with this key sequence. - Custom margins for BufferControl. Fixes: - Don't trigger autocompletion on paste. - Added `pre_run` parameter to CommandLineInterface. - Correct invalidation of BeforeInput and AfterInput. - Correctly handle transparency. (For floats.) - Small change in the algorithm to determine Window dimensions: keep in the bounds of the Window dimensions. Backwards-incompatible changes: - There a now a `Lexer` abstract base class. Every lexer should be an instance of that class, and Pygments lexers should be wrapped in a `PygmentsLexer` class. `prompt_toolkit.shortcuts` still accepts Pygments lexers directly for backwards-compatibility. - BufferControl no longer has a `show_line_numbers` argument. Pass a `NumberedMargin` instance instead. - The `History` class became an abstract base class and only defines an interface. The default history class is now called `InMemoryHistory`. 0.46: 2015-08-08 ---------------- New features: - By default, in shortcuts, only show search highlighting when the search is the current input buffer. - Accept 'count' for all search operations. (For repetition.) - `shortcuts.create_default_layout` accepts a `multiline` parameter. - Show meta display text for completions also in multi-column mode. Fixes: - Correct invalidation of DefaultPrompt when search direction changes. - Correctly include/exclude current cursor position in search. - More consistency in styles. - Fix in ConditionalProcessor.has_focus. - Python 2.6 compatibility fix. - Show cursor at the correct position during reverse-i-search. - Fixed stdout encoding bug for vt100 output. Backwards-incompatible changes: - Use of `ConditionalContainer` everywhere. The `Window` class no longer accepts a `filter` argument to decide about the visibility. Instead wrapping inside a `ConditionalContainer` class is required. 0.45: 2015-07-30 ---------------- Fixes: - Bug fix on OS X: correctly detect platform as not Windows. 0.44: 2015-07-30 ---------------- Fixes: - Fixed bug in eventloops: handle timeout correctly, even when there is an eventhook. - Bug fix in open-in-editor: set correct cursor position. New features: - CompletionsMenu got a scroll_offset. - Use 256 colors and ANSI sequences when ConEmu ANSI support has been detected. - Added PyperclipClipboard for synchronisation with the system clipboard. and clipboard parameter in shortcut functions. - Filter for enabling/disabling handling of Vi 'v' binding. 0.43: 2015-07-15 ---------------- Fixes: - Windows bug fix. STD_INPUT_HANDLE should be c_ulong instead of HANDLE. (This caused crashes on some systems.) New features: - Added eventloop and patch_stdout parameters to get_input. - Inputhook support added. - Added ShowLeadingWhiteSpaceProcessor and ShowTrailingWhiteSpaceProcessor processors. - Accept Filter as multiline parameter in 'shortcuts'. - MultiColumnCompletionsMenu + display_completions_in_columns parameter in shortcuts. Backwards incompatible changes: - Layout.width was renamed to preferred_width and now receives a max_available_width parameter. 0.42: 2015-06-25 ---------------- Fixes: - Support for Windows cmder and conemu consoles. - Correct handling of unicode input and output on Windows. New features: - Support terminal titles. - Handle Control-Enter as Meta-Enter on Windows. - Control-Z key binding for Windows. - Implemented alternate screen buffer on Windows. - Clipboard became an ABC and InMemoryClipboard default implementation. 0.41: 2015-06-20 ---------------- Fixes: - Emacs Control-T key binding. - Color fix for Windows consoles. New features: - Allow both booleans and Filters in many places. - `password` can be a Filter now. 0.40: 2015-06-15 ---------------- Fixes: - Fix in output_screen_diff: reset correctly. - Ignore flush errors in vt100_output. - Implemented gg Vi key binding. - Bug fix in the renderer when the style changes. New features: - TokenListControl can now display the cursor somewhere. - Added SwitchableValidator class. - print_tokens function added. - get_style argument for Application added. - KeyBindingManager got an enable_all argument. Backwards incompatible changes: - history_search is now a SimpleFilter instance. 0.39: 2015-06-04 ---------------- Fixes: - Fixed layout.py example. - Fixed eventloop for Python 64bit on Windows. - Fix in history. - Fix in key bindings. 0.38: 2015-05-31 ---------------- New features: - Improved performance significantly for processing key bindings. (Pasting text will be a lot faster.) - Added 'M' Vi key binding. - Added 'z-' and 'z+' and 'z-[Enter]' Vi keybindings. - Correctly handle input and output encodings on Windows. Bug fixes: - Fix bug when completion cursor position is outside range of current text. - Don't crash Control-D is pressed while waiting for ENTER press (in run_system_command.) - On Ctrl-Z, don't suspend on Windows, where we don't have SIGTSTP. - Ignore result when open_in_editor received a nonzero return code. - Bug fix in displaying of menu meta information. Don't show 'None'. Backwards incompatible changes: - Refactoring of the I/O layer. Separation of the CommandLineInterface and Application class. - Renamed enable_system_prompt to enable_system_bindings. 0.37: 2015-05-11 ---------------- New features: - Handling of trailing input in contrib.regular_languages. Bug fixes: - Default message in shortcuts.get_input. - Windows compatibility for contrib.telnet. - OS X bugfix in contrib.telnet. 0.36: 2015-05-09 ---------------- New features: - Added get_prompt_tokens parameter to create_default_layout. - Show prompt in bold by default. Bug fixes: - Correct cache invalidation of DefaultPrompt. - Using text_type assertions in contrib.telnet. - Removed contrib.shortcuts completely. (The .pyc files still appeared incorrectly in the wheel.) 0.35: 2015-05-07 ---------------- New features: - WORD parameter for WordCompleter. - DefaultPrompt.from_message constructor. - Added reactive.py for simple integer data binding. - Implemented scroll_offset and scroll_beyond_bottom for Window. - Some performance improvements. Bug fixes: - Handling of relative path in PathCompleter. - unicode_literals for all examples. - Visibility of bottom toolbar in create_default_layout shortcut. - Correctly handle 'J' vi key binding. - Fix in indent/unindent. - Better Vi bindings in visual mode. Backwards incompatible changes: - Moved prompt_toolkit.contrib.shortcuts to prompt_toolkit.shortcuts. - Refactoring of contrib.telnet. 0.34: 2015-04-26 ---------------- Bug fixes: - Correct display of multi width characters in completion menu. Backwards incompatible changes: - Renamed Buffer.add_to_history to Buffer.append_to_history. 0.33: 2015-04-25 ---------------- Bug fixes: - Crash fixed in SystemCompleter when some directories didn't exist. - Made text/cursor_position in Document more atomic. - Fixed Char.__ne__, improves performance. - Better performance of the filter module. - Refactoring of the filter module. - Bugfix in BufferControl, caching was not done correctly. - fixed 'zz' Vi key binding. New features: - Do tilde expansion for system commands. - Added ignore_case option for CommandLineInterface. Backwards incompatible changes: - complete_while_typing parameter has been moved from CommandLineInterface to Buffer. 0.32: 2015-04-22 ---------------- New features: - Implemented repeat arg for '{' and '}' vi key binding. - Added autocorrection example. - first experimental telnet interface added. - Added contrib.validators.SentenceValidator. - Added Layout.walk generator to traverse the layout. - Improved 'L' and 'H' Vi key bindings. - Implemented Vi 'zz' key binding. - ValidationToolbar got a show_position parameter. - When only width or height are given for a float, the control is centered in the parent. - Added beforeKeyPress and afterKeyPress events. - Added HighlightMatchingBracketProcessor. - SearchToolbar got a vi_mode option to show '?' and '/' instead of 'I-search'. - Implemented vi '*' binding. - Added onBufferChanged event to CommandLineInterface. - Many performance improvements: some caching and not rendering after every single key stroke. - Added ConditionalProcessor. - Floating menus are now shown above the cursor, when below is not enough space, but above is enough space. - Improved vi 'G' key binding. - WindowRenderInfo got a full_height_visible, top_visible, and a few other attributes. - PathCompleter got an expanduser option to do tilde expansion. Fixed: - Always insert indentation when pressing enter. - vertical_scroll should be an int instead of a float. - Some bug fixes in renderer.Output. - Pressing backspace in an empty search in Vi mode now goes back to navigation mode. - Bug fix in TokenListControl (Correctly calculate height for multiline content.) - Only apply HighlightMatchingBracketProcessor when editing buffer. - Ensure that floating layouts never go out of bounds. - Home/End now go to the start and end of the line. - Fixed vi 'c' key binding. - Redraw the whole output when the style changes. - Don't trigger onTextInsert when working_index doesn't change. - Searching now wraps around the start/end of buffer/history. - Don't go to the start of the line when moving forward in history. Changes: - Don't show directory/file/link in the meta information of PathCompleter anymore. - Complete refactoring of the event loops. - Refactoring of the Renderer and CommandLineInterface class. - CommandLineInterface now accepts an optional Output argument. - CommandLineInterface now accepts a use_alternate_screen parameter. - Moved highlighting code for search/selection from BufferControl to processors. - Completers are now always run asynchronously. - Complete refactoring of the search. (Most responsibility move out of Buffer class. CommandLineInterface now got a search_state attribute.) Backwards incompatible changes: - get_input does now have a history attribute instead of history_filename. - EOFError and KeyboardInterrupt is raised for abort and exit instead of custom exceptions. - CommandLineInterface does no longer have a property 'is_reading_input'. - filters.AlwaysOn/AlwaysOff have been renamed to Always/Never. - AcceptAction has been moved from CommandLineInterface to Buffer. Now every buffer can define its own accept action. - CommandLineInterface now expects an Eventloop instance in __init__. 0.31: 2015-01-30 ---------------- Fixed: - Bug in float positioning - Show completion menu only for the default_buffer in get_input. New features: - PathCompleter got a get_paths parameter. - PathCompleter sorts alphabetically. - Added contrib.completers.SystemCompleter - Completion got a get_display_meta parameter. 0.30: 2015-01-26 ---------------- Fixed: - Backward compatibility with django_extensions. - Usage of alternate screen in the renderer. New features: - Vi '#' key binding. - contrib.shortcuts.get_input got a get_bottom_toolbar_tokens argument. - Separate key bindings for "open in editor." KeyBindingManager got a enable_open_in_editor argument. 0.28: 2015-01-25 ---------------- Fixed: - syntax error in 0.27 0.27: 2015-01-25 ---------------- Backwards-incompatible changes: - Complete refactoring of the layout system. (HSplit, VSplit, FloatContainer) as well as a list of controls (TokenListControl, BufferControl) in order to design much more complex layouts. - ptpython code has been moved to a separate repository. New features: - prompt_toolkit.contrib.shortcuts.get_input has been extended. Fixed: - Behaviour of Control+left/right/up/down. - Backspace in incremental search. - Hide completion menu correctly when the cursor position changes. 0.26: 2015-01-08 ---------------- Backwards-incompatible changes: - Refactoring of the key input system. (The registry which contains the key bindings, the focus stack, key binding manager.) Overall much better API. - Renamed `Line` to `Buffer`. New features: - Added filters as a way of disabling/enabling parts of the runtime according to certain conditions. - Global clipboard, shared between all buffers. - Added (experimental) "merge history" feature to ptpython. - Added 'C-x r k' and 'C-x r y' emacs key bindings for cut and paste. - Added g_, ge and gE vi key bindings. - Added support for handling control + arrows keys. Fixed: - Correctly handle f1-f4 in rxvt-unicode. 0.25: 2014-12-11 ---------------- Fixed: - Package did not install on Python 2.6/2.7. 0.24: 2014-12-10 ---------------- Backwards-incompatible changes: - Completer.get_completions now gets a complete_event argument. New features: - For ptpython: filename completion inside Python strings. - prompt_toolkit.contrib.regular_languages added. - prompt_toolkit.contrib.pdb added. (Experimental PDB front-end.) - Support for multiline toolbars. - asyncio support added. (Integration with asyncio event loop.) - WORD parameter added to Document.word_before_cursor. Fixed: - Small fixes in Win32 terminal output. - Bug fix in parsing of CPR response. 0.23: 2014-11-28 ---------------- New features: - contrib.completers added. Fixed: - Improved j/k key bindings in Vi mode. - Don't leak internal variables into ptipython shell. - Initialize IPython extensions. - Use IPython's prompt. - Workarounds for Jedi crashes. 0.22: 2014-11-09 ---------------- Fixed: - Fixed missing import which caused Ctrl-Z to crash. - Show error message for ptipython when IPython is not installed. 0.21: 2014-10-25 ---------------- New features: - Using entry_points in setup.py - Experimental Win32 support added. Fixed: - Behaviour of 'r' and 'R' key bindings in Vi mode. - Detect multiline correctly for ptpython when there are triple quoted strings. - Some other small improvements. 0.20: 2014-10-04 ---------------- Fixed: - Workarounds for Jedi bugs. - Better handling of window resize events. - Fixed counter in ptipython prompt. - Use IPythonInputSplitter.transform_cell for IPython syntax validation. - Only insert newlines for open brackets if the cursor is at the end of the input string. New features: - More Vi key bindings: 'B', 'W', 'E', 'aW', 'aw' and 'iW' - ControlZ now suspends the process 0.19: 2014-09-30 ---------------- Fixed: - Handle Jedi crashes. - Autocompletion in `ptipython` - Input validation in `ptipython` - Execution of system commands (in `ptpython`) in Python 3 - Add current directory to sys.path for `ptpython`. - Minimal jedi and six version in setup.py New features - Python 2.6 support - C-C> and C-C< indent and unindent emacs key bindings. - `ptpython` can now also run python scripts, so aliasing of `ptpython` as `python` will work better. 0.18: 2014-09-29 ---------------- - First official (beta) release. Jan 25, 2014 ------------ first commit prompt_toolkit-2.0.10/LICENSE0000644000175100017510000000272513545407022017325 0ustar jonathanjonathan00000000000000Copyright (c) 2014, Jonathan Slenders All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the {organization} nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. prompt_toolkit-2.0.10/MANIFEST.in0000644000175100017510000000020513545407022020045 0ustar jonathanjonathan00000000000000include *rst LICENSE CHANGELOG MANIFEST.in recursive-include examples *.py recursive-include tests *.py prune examples/sample?/build prompt_toolkit-2.0.10/PKG-INFO0000644000175100017510000003164013545410361017413 0ustar jonathanjonathan00000000000000Metadata-Version: 1.2 Name: prompt_toolkit Version: 2.0.10 Summary: Library for building powerful interactive command lines in Python Home-page: https://github.com/jonathanslenders/python-prompt-toolkit Author: Jonathan Slenders License: BSD-3-Clause Description: Python Prompt Toolkit ===================== |Build Status| |AppVeyor| |PyPI| |RTD| |License| |Codecov| .. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png *``prompt_toolkit`` is a library for building powerful interactive command line applications in Python.* Read the `documentation on readthedocs `_. NOTICE: prompt_toolkit 2.0 ************************** Please notice that this is prompt_toolkit 2.0. It is incompatible with the 1.0 branch, but much better in many regards. Many applications are still using prompt_toolkit 1.0, but upgrading is strongly recommended. Feel free to open a new issue if you don't manage to upgrade to prompt_toolkit 2.0. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt_toolkit. .. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/ptpython.png prompt_toolkit features *********************** ``prompt_toolkit`` could be a replacement for `GNU readline `_, but it can be much more than that. Some features: - Pure Python. - Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) - Multi-line input editing. - Advanced code completion. - Both Emacs and Vi key bindings. (Similar to readline.) - Even some advanced Vi functionality, like named registers and digraphs. - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.7. - Works well with Unicode double width characters. (Chinese input.) - Selecting text for copy/paste. (Both Emacs and Vi style.) - Support for `bracketed paste `_. - Mouse support for cursor positioning and scrolling. - Auto suggestions. (Like `fish shell `_.) - Multiple input buffers. - No global state. - Lightweight, the only dependencies are Pygments, six and wcwidth. - Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. - And much more... Feel free to create tickets for bugs and feature requests, and create pull requests if you have nice patches that you would like to share with others. About Windows support ********************* ``prompt_toolkit`` is cross platform, and everything that you build on top should run fine on both Unix and Windows systems. On Windows, it uses a different event loop (``WaitForMultipleObjects`` instead of ``select``), and another input and output system. (Win32 APIs instead of pseudo-terminals and VT100.) It's worth noting that the implementation is a "best effort of what is possible". Both Unix and Windows terminals have their limitations. But in general, the Unix experience will still be a little better. For Windows, it's recommended to use either `cmder `_ or `conemu `_. Installation ************ :: pip install prompt_toolkit For Conda, do: :: conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit Getting started *************** The most simple example of the library would look like this: .. code:: python from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) For more complex examples, have a look in the ``examples`` directory. All examples are chosen to demonstrate only one thing. Also, don't be afraid to look at the source code. The implementation of the ``prompt`` function could be a good start. Note for Python 2: all strings are expected to be unicode strings. So, either put a small ``u`` in front of every string or put ``from __future__ import unicode_literals`` at the start of the above example. Projects using prompt_toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql client. - `litecli `_: SQLite client. - `wharfee `_: A Docker command line. - `xonsh `_: A Python-ish, BASHwards-compatible shell. - `saws `_: A Supercharged AWS Command Line Interface. - `cycli `_: A Command Line Interface for Cypher. - `crash `_: Crate command line client. - `vcli `_: Vertica client. - `aws-shell `_: An integrated shell for working with the AWS CLI. - `softlayer-python `_: A command-line interface to manage various SoftLayer products and services. - `ipython `_: The IPython REPL - `click-repl `_: Subcommand REPL for click apps. - `haxor-news `_: A Hacker News CLI. - `gitsome `_: A Git/Shell Autocompleter with GitHub Integration. - `http-prompt `_: An interactive command-line HTTP client. - `coconut `_: Functional programming in Python. - `Ergonomica `_: A Bash alternative written in Python. - `Kube-shell `_: Kubernetes shell: An integrated shell for working with the Kubernetes CLI - `mssql-cli `_: A command-line client for Microsoft SQL Server. - `robotframework-debuglibrary `_: A debug library and REPL for RobotFramework. - `ptrepl `_: Run any command as REPL - `clipwdmgr `_: Command Line Password Manager. - `slacker `_: Easy access to the Slack API and admin of workspaces via REPL. - `EdgeDB `_: The next generation object-relational database. - `pywit `_: Python library for Wit.ai. - `objection `_: Runtime Mobile Exploration. - `habu `_: Python Network Hacking Toolkit. - `nawano `_: Nano cryptocurrency wallet - `athenacli `_: A CLI for AWS Athena. - `vulcano `_: A framework for creating command-line applications that also runs in REPL mode. - `kafka-shell `_: A supercharged shell for Apache Kafka. Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure Python. - `freud `_: REST client backed by SQLite for storing servers - `pypager `_: A $PAGER in pure Python (like "less"). - `kubeterminal `_: Kubectl helper tool. Libraries: - `ptterm `_: A terminal emulator widget for prompt_toolkit. - `PyInquirer `_: A Python library that wants to make it easy for existing Inquirer.js users to write immersive command line applications in Python. (Want your own project to be listed here? Please create a GitHub issue.) Philosophy ********** The source code of ``prompt_toolkit`` should be readable, concise and efficient. We prefer short functions focusing each on one task and for which the input and output types are clearly specified. We mostly prefer composition over inheritance, because inheritance can result in too much functionality in the same object. We prefer immutable objects where possible (objects don't change after initialization). Reusability is important. We absolutely refrain from having a changing global state, it should be possible to have multiple independent instances of the same code in the same process. The architecture should be layered: the lower levels operate on primitive operations and data structures giving -- when correctly combined -- all the possible flexibility; while at the higher level, there should be a simpler API, ready-to-use and sufficient for most use cases. Thinking about algorithms and efficiency is important, but avoid premature optimization. Special thanks to ***************** - `Pygments `_: Syntax highlighter. - `wcwidth `_: Determine columns needed for a wide characters. .. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/prompt-toolkit/python-prompt-toolkit# .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true :target: https://ci.appveyor.com/project/prompt-toolkit/python-prompt-toolkit/ .. |RTD| image:: https://readthedocs.org/projects/python-prompt-toolkit/badge/ :target: https://python-prompt-toolkit.readthedocs.io/en/master/ .. |License| image:: https://img.shields.io/github/license/prompt-toolkit/python-prompt-toolkit.svg :target: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/LICENSE .. |Codecov| image:: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/branch/master/graphs/badge.svg?style=flat :target: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/ Other libraries and implementations in other languages ****************************************************** - `go-prompt `_: building a powerful interactive prompt in Go, inspired by python-prompt-toolkit. - `urwid `_: Console user interface library for Python. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python Classifier: Topic :: Software Development Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* prompt_toolkit-2.0.10/README.rst0000644000175100017510000002415213545407204020007 0ustar jonathanjonathan00000000000000Python Prompt Toolkit ===================== |Build Status| |AppVeyor| |PyPI| |RTD| |License| |Codecov| .. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png *``prompt_toolkit`` is a library for building powerful interactive command line applications in Python.* Read the `documentation on readthedocs `_. NOTICE: prompt_toolkit 2.0 ************************** Please notice that this is prompt_toolkit 2.0. It is incompatible with the 1.0 branch, but much better in many regards. Many applications are still using prompt_toolkit 1.0, but upgrading is strongly recommended. Feel free to open a new issue if you don't manage to upgrade to prompt_toolkit 2.0. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt_toolkit. .. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/ptpython.png prompt_toolkit features *********************** ``prompt_toolkit`` could be a replacement for `GNU readline `_, but it can be much more than that. Some features: - Pure Python. - Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) - Multi-line input editing. - Advanced code completion. - Both Emacs and Vi key bindings. (Similar to readline.) - Even some advanced Vi functionality, like named registers and digraphs. - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.7. - Works well with Unicode double width characters. (Chinese input.) - Selecting text for copy/paste. (Both Emacs and Vi style.) - Support for `bracketed paste `_. - Mouse support for cursor positioning and scrolling. - Auto suggestions. (Like `fish shell `_.) - Multiple input buffers. - No global state. - Lightweight, the only dependencies are Pygments, six and wcwidth. - Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. - And much more... Feel free to create tickets for bugs and feature requests, and create pull requests if you have nice patches that you would like to share with others. About Windows support ********************* ``prompt_toolkit`` is cross platform, and everything that you build on top should run fine on both Unix and Windows systems. On Windows, it uses a different event loop (``WaitForMultipleObjects`` instead of ``select``), and another input and output system. (Win32 APIs instead of pseudo-terminals and VT100.) It's worth noting that the implementation is a "best effort of what is possible". Both Unix and Windows terminals have their limitations. But in general, the Unix experience will still be a little better. For Windows, it's recommended to use either `cmder `_ or `conemu `_. Installation ************ :: pip install prompt_toolkit For Conda, do: :: conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit Getting started *************** The most simple example of the library would look like this: .. code:: python from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) For more complex examples, have a look in the ``examples`` directory. All examples are chosen to demonstrate only one thing. Also, don't be afraid to look at the source code. The implementation of the ``prompt`` function could be a good start. Note for Python 2: all strings are expected to be unicode strings. So, either put a small ``u`` in front of every string or put ``from __future__ import unicode_literals`` at the start of the above example. Projects using prompt_toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql client. - `litecli `_: SQLite client. - `wharfee `_: A Docker command line. - `xonsh `_: A Python-ish, BASHwards-compatible shell. - `saws `_: A Supercharged AWS Command Line Interface. - `cycli `_: A Command Line Interface for Cypher. - `crash `_: Crate command line client. - `vcli `_: Vertica client. - `aws-shell `_: An integrated shell for working with the AWS CLI. - `softlayer-python `_: A command-line interface to manage various SoftLayer products and services. - `ipython `_: The IPython REPL - `click-repl `_: Subcommand REPL for click apps. - `haxor-news `_: A Hacker News CLI. - `gitsome `_: A Git/Shell Autocompleter with GitHub Integration. - `http-prompt `_: An interactive command-line HTTP client. - `coconut `_: Functional programming in Python. - `Ergonomica `_: A Bash alternative written in Python. - `Kube-shell `_: Kubernetes shell: An integrated shell for working with the Kubernetes CLI - `mssql-cli `_: A command-line client for Microsoft SQL Server. - `robotframework-debuglibrary `_: A debug library and REPL for RobotFramework. - `ptrepl `_: Run any command as REPL - `clipwdmgr `_: Command Line Password Manager. - `slacker `_: Easy access to the Slack API and admin of workspaces via REPL. - `EdgeDB `_: The next generation object-relational database. - `pywit `_: Python library for Wit.ai. - `objection `_: Runtime Mobile Exploration. - `habu `_: Python Network Hacking Toolkit. - `nawano `_: Nano cryptocurrency wallet - `athenacli `_: A CLI for AWS Athena. - `vulcano `_: A framework for creating command-line applications that also runs in REPL mode. - `kafka-shell `_: A supercharged shell for Apache Kafka. Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure Python. - `freud `_: REST client backed by SQLite for storing servers - `pypager `_: A $PAGER in pure Python (like "less"). - `kubeterminal `_: Kubectl helper tool. Libraries: - `ptterm `_: A terminal emulator widget for prompt_toolkit. - `PyInquirer `_: A Python library that wants to make it easy for existing Inquirer.js users to write immersive command line applications in Python. (Want your own project to be listed here? Please create a GitHub issue.) Philosophy ********** The source code of ``prompt_toolkit`` should be readable, concise and efficient. We prefer short functions focusing each on one task and for which the input and output types are clearly specified. We mostly prefer composition over inheritance, because inheritance can result in too much functionality in the same object. We prefer immutable objects where possible (objects don't change after initialization). Reusability is important. We absolutely refrain from having a changing global state, it should be possible to have multiple independent instances of the same code in the same process. The architecture should be layered: the lower levels operate on primitive operations and data structures giving -- when correctly combined -- all the possible flexibility; while at the higher level, there should be a simpler API, ready-to-use and sufficient for most use cases. Thinking about algorithms and efficiency is important, but avoid premature optimization. Special thanks to ***************** - `Pygments `_: Syntax highlighter. - `wcwidth `_: Determine columns needed for a wide characters. .. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/prompt-toolkit/python-prompt-toolkit# .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true :target: https://ci.appveyor.com/project/prompt-toolkit/python-prompt-toolkit/ .. |RTD| image:: https://readthedocs.org/projects/python-prompt-toolkit/badge/ :target: https://python-prompt-toolkit.readthedocs.io/en/master/ .. |License| image:: https://img.shields.io/github/license/prompt-toolkit/python-prompt-toolkit.svg :target: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/LICENSE .. |Codecov| image:: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/branch/master/graphs/badge.svg?style=flat :target: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/ Other libraries and implementations in other languages ****************************************************** - `go-prompt `_: building a powerful interactive prompt in Go, inspired by python-prompt-toolkit. - `urwid `_: Console user interface library for Python. prompt_toolkit-2.0.10/examples/0000755000175100017510000000000013545410361020130 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/dialogs/0000755000175100017510000000000013545410361021552 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/dialogs/button_dialog.py0000755000175100017510000000072413545407204024766 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of button dialog window. """ from __future__ import unicode_literals 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), ], ) print('Result = {}'.format(result)) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/dialogs/input_dialog.py0000755000175100017510000000054013545407204024606 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of an input box dialog. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import input_dialog def main(): result = input_dialog( title='Input dialog example', text='Please type your name:') print('Result = {}'.format(result)) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/dialogs/messagebox.py0000755000175100017510000000051413545407204024266 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a message box window. """ from __future__ import unicode_literals 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.') if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/dialogs/password_dialog.py0000755000175100017510000000060313545407204025311 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of an password input dialog. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import input_dialog def main(): result = input_dialog( title='Password dialog example', text='Please type your password:', password=True) print('Result = {}'.format(result)) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/dialogs/progress_dialog.py0000755000175100017510000000226213545407204025316 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a progress bar dialog. """ from __future__ import unicode_literals 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('{} / {}\n'.format(dirpath, f)) set_percentage(percentage + 1) percentage += 2 time.sleep(.1) if percentage == 100: break if percentage == 100: break # Show 100% for a second, before quitting. set_percentage(100) time.sleep(1) return def main(): progress_dialog( title='Progress dialog example', text='As an examples, we walk through the filesystem and print ' 'all directories', run_callback=worker) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/dialogs/radio_dialog.py0000755000175100017510000000211113545407204024541 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a radio list box dialog. """ from __future__ import unicode_literals 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:') print('Result = {}'.format(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:') print('Result = {}'.format(result)) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/dialogs/styled_messagebox.py0000755000175100017510000000165713545407204025663 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/dialogs/yes_no_dialog.py0000755000175100017510000000056413545407204024751 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of confirmation (yes/no) dialog window. """ from __future__ import unicode_literals 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?') print('Result = {}'.format(result)) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/full-screen/0000755000175100017510000000000013545410361022347 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/full-screen/buttons.py0000755000175100017510000000456013545407204024431 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple example of a few buttons and click handlers. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/calculator.py0000755000175100017510000000530113545407204025056 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple example of a calculator program. This could be used as inspiration for a REPL. """ from __future__ import unicode_literals from prompt_toolkit.application import Application from prompt_toolkit.document import Document from prompt_toolkit.filters import has_focus 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 = '\n\nIn: {}\nOut: {}'.format( input_field.text, eval(input_field.text)) # Don't do 'eval' in real code! except BaseException as e: output = '\n\n{}'.format(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() prompt_toolkit-2.0.10/examples/full-screen/dummy-app.py0000755000175100017510000000023713545407022024637 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ This is the most simple example possible. """ from prompt_toolkit import Application app = Application(full_screen=False) app.run() prompt_toolkit-2.0.10/examples/full-screen/full-screen-demo.py0000755000175100017510000001131413545407204026067 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ """ from __future__ import unicode_literals 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. 'radiolist focused': 'noreverse', 'radiolist focused radio.selected': 'reverse', '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() prompt_toolkit-2.0.10/examples/full-screen/hello-world-asyncio.py0000755000175100017510000000230213545407204026616 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple example of a a text area displaying "Hello World!". """ from __future__ import unicode_literals import asyncio from prompt_toolkit.application import Application from prompt_toolkit.eventloop import use_asyncio_event_loop 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(): # Tell prompt_toolkit to use asyncio. use_asyncio_event_loop() # Run application async. asyncio.get_event_loop().run_until_complete( application.run_async().to_asyncio_future()) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/full-screen/hello-world.py0000755000175100017510000000170113545407204025155 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple example of a a text area displaying "Hello World!". """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/no-layout.py0000644000175100017510000000023313545407022024646 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ An empty full screen application without layout. """ from prompt_toolkit import Application Application(full_screen=True).run() prompt_toolkit-2.0.10/examples/full-screen/pager.py0000755000175100017510000000452413545407204024031 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple application that shows a Pager application. """ from __future__ import unicode_literals 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. with open('./pager.py', 'rb') as f: text = f.read().decode('utf-8') def get_statusbar_text(): return [ ('class:status', './pager.py - '), ('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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/0000755000175100017510000000000013545410361024745 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/full-screen/simple-demos/alignment.py0000755000175100017510000000367713545407204027317 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demo of the different Window alignment options. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/autocompletion.py0000755000175100017510000000353713545407204030376 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/colorcolumn.py0000755000175100017510000000356113545407204027665 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Colorcolumn example. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/cursorcolumn-cursorline.py0000755000175100017510000000356213545407204032250 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Cursorcolumn / cursorline example. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/float-transparency.py0000755000175100017510000000523013545407204031140 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of the 'transparency' attribute of `Window' when used in a Float. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/floats.py0000755000175100017510000000624113545407204026617 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Horizontal split example. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/focus.py0000755000175100017510000000545313545407204026452 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of how to programmatically focus a certain widget. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/horizontal-align.py0000755000175100017510000000727413545407204030617 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Horizontal align demo with HSplit. """ from __future__ import unicode_literals 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.dimension import D 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/horizontal-split.py0000755000175100017510000000172513545407204030653 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Horizontal split example. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/line-prefixes.py0000755000175100017510000000573413545407204030107 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals from prompt_toolkit.application import Application from prompt_toolkit.buffer import Buffer 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.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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/margins.py0000755000175100017510000000421513545407204026766 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/vertical-align.py0000755000175100017510000000723613545407204030235 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Vertical align demo with VSplit. """ from __future__ import unicode_literals 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.dimension import D 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() prompt_toolkit-2.0.10/examples/full-screen/simple-demos/vertical-split.py0000755000175100017510000000172013545407204030266 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Vertical split example. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/full-screen/split-screen.py0000755000175100017510000001177113545407204025345 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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 behaviour. @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() prompt_toolkit-2.0.10/examples/full-screen/text-editor.py0000755000175100017510000002104113545407204025174 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple example of a Notepad-like text editor. """ from __future__ import unicode_literals import datetime from prompt_toolkit.application import Application from prompt_toolkit.application.current import get_app from prompt_toolkit.completion import PathCompleter from prompt_toolkit.eventloop import From, Future, Return, ensure_future 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(object): 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(object): 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(): def coroutine(): global current_path open_dialog = TextInputDialog( title='Open file', label_text='Enter the path of a file:', completer=PathCompleter()) path = yield From(show_dialog_as_float(open_dialog)) 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 IOError as e: show_message('Error', '{}'.format(e)) ensure_future(coroutine()) def do_about(): show_message('About', 'Text editor demo.\nCreated by Jonathan Slenders.') def show_message(title, text): def coroutine(): dialog = MessageDialog(title, text) yield From(show_dialog_as_float(dialog)) ensure_future(coroutine()) 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 = yield dialog.future app.layout.focus(focused_before) if float_ in root_container.floats: root_container.floats.remove(float_) raise 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(): def coroutine(): dialog = TextInputDialog( title='Go to line', label_text='Line number:') line_number = yield From(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() prompt_toolkit-2.0.10/examples/gevent-get-input.py0000755000175100017510000000132513545407204023712 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ For testing: test to make sure that everything still works when gevent monkey patches are applied. """ from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/other/0000755000175100017510000000000013545410361021251 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/other/coroutines-and-futures.py0000755000175100017510000000711513545407204026261 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ An example of a prompt_toolkit coroutine and `Future` objects. Probably you won't need this in your own applications, but it can be helpful for understanding how the `prompt_toolkit` event loops works. If you need asynchronous programming in your own application, it could be worth considering to use asyncio instead. The reason that prompt_toolkit implements its own event loop is because we still need to support Python 2, but it does run on top of asyncio as well if needed. """ import time from prompt_toolkit.eventloop import ( From, Future, Return, ensure_future, get_event_loop, run_in_executor, ) # # The following functions are all asynchronous functions that return a # prompt_toolkit `Future` object. # def async_1(): """ Runs some code in an executor (other thread). """ def in_executor(): time.sleep(1) return 'Hello from async_1' return run_in_executor(in_executor) def async_2(): """ Raise an exception in the executor. """ def in_executor(): time.sleep(.2) raise Exception('Failure from async_2') return run_in_executor(in_executor) def async_3(): " Succeed immediately. " return Future.succeed('Hello from async_3') def async_4(): " Fail immediately. " return Future.fail(Exception('Failure from async_4')) def async_5(): " Create a `Future` and call `set_result` later on. " f = Future() def in_executor(): time.sleep(.2) f.set_result('Hello from async_5') run_in_executor(in_executor) return f def async_6(): " Create a `Future` and call `set_exception` later on. " f = Future() def in_executor(): time.sleep(.2) f.set_exception(Exception('Failure from async_6')) run_in_executor(in_executor) return f # # Here we have the main coroutine that calls the previous asynchronous # functions, each time waiting for the result before calling the next function. # The coroutine needs to passed to `ensure_future` in order to run it in the # event loop. # def my_coroutine(): """ A coroutine example. "yield-from" is used to wait for a `Future` or another coroutine. """ # Wait for first future. value = yield From(async_1()) print('async_1 returned: ', value) # Wait for second future. try: value = yield From(async_2()) except Exception as e: print('async_2 raised: ', e) else: print('async_2 returned: ', value) # Wait for third future. value = yield From(async_3()) print('async_3 returned: ', value) # Wait for fourth future. try: value = yield From(async_4()) except Exception as e: print('async_4 raised: ', e) else: print('async_4 returned: ', value) # Wait for fifth future. value = yield From(async_5()) print('async_5 returned: ', value) # Wait for sixth future. try: value = yield From(async_6()) except Exception as e: print('async_6 raised: ', e) else: print('async_6 returned: ', value) # Wait for another coroutine. value = yield From(other_coroutine()) print('other_coroutine returned: ', value) raise Return('result') def other_coroutine(): value = yield From(Future.succeed(True)) value = yield From(Future.succeed(True)) raise Return('Result from coroutine.') def main(): loop = get_event_loop() f = ensure_future(my_coroutine()) # Run the event loop, until the coroutine is done. loop.run_until_complete(f) print(f.result()) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/print-text/0000755000175100017510000000000013545410361022246 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/print-text/ansi-colors.py0000755000175100017510000000433613545407204025064 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of all the ANSI colors. """ from __future__ import print_function, unicode_literals 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() prompt_toolkit-2.0.10/examples/print-text/ansi.py0000755000175100017510000000233413545407204023561 0ustar jonathanjonathan00000000000000#!/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 __future__ import print_function, unicode_literals 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[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() prompt_toolkit-2.0.10/examples/print-text/html.py0000755000175100017510000000255413545407204023577 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of how to print using the HTML class. """ from __future__ import print_function, unicode_literals 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(' 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() prompt_toolkit-2.0.10/examples/print-text/named-colors.py0000755000175100017510000000167313545407204025217 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of all the ANSI colors. """ from __future__ import print_function, unicode_literals 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() prompt_toolkit-2.0.10/examples/print-text/print-formatted-text.py0000755000175100017510000000210513545407204026724 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of printing colored text to the output. """ from __future__ import print_function, unicode_literals 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() prompt_toolkit-2.0.10/examples/print-text/print-frame.py0000755000175100017510000000060313545407204025050 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example usage of 'print_container', a tool to print any layout in a non-interactive way. """ from __future__ import print_function, unicode_literals 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', )) prompt_toolkit-2.0.10/examples/print-text/pygments-tokens.py0000755000175100017510000000233513545407204025777 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Printing a list of Pygments (Token, text) tuples, or an output of a Pygments lexer. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/print-text/true-color-demo.py0000755000175100017510000000213113545407204025637 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of all the ANSI colors. """ from __future__ import print_function, unicode_literals 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() prompt_toolkit-2.0.10/examples/progress-bar/0000755000175100017510000000000013545410361022536 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/progress-bar/a-lot-of-parallel-tasks.py0000755000175100017510000000237513545407204027457 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ More complex demonstration of what's possible with the progress bar. """ from __future__ import unicode_literals 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): for i in pb(range(total), label=label): 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. threads.append(threading.Thread(target=run_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=.5) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/colored-title-and-label.py0000755000175100017510000000110113545407204027471 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A progress bar that displays a formatted title above the progress bar and has a colored label. """ from __future__ import unicode_literals 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(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/custom-key-bindings.py0000755000175100017510000000232513545407204027012 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A very simple progress bar which keep track of the progress as we consume an iterator. """ from __future__ import unicode_literals 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(.01) if cancel[0]: break if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/many-parallel-tasks.py0000755000175100017510000000313213545407204026775 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ More complex demonstration of what's possible with the progress bar. """ from __future__ import unicode_literals 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, .1)), threading.Thread(target=run_task, args=('Second task', 100, .1)), threading.Thread(target=run_task, args=('Third task', 8, 3)), threading.Thread(target=run_task, args=('Fourth task', 200, .1)), threading.Thread(target=run_task, args=('Fifth task', 40, .2)), threading.Thread(target=run_task, args=('Sixth task', 220, .1)), threading.Thread(target=run_task, args=('Seventh task', 85, .05)), threading.Thread(target=run_task, args=('Eight task', 200, .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=.5) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/nested-progress-bars.py0000755000175100017510000000114613545407204027170 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of nested progress bars. """ from __future__ import unicode_literals 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='Subtask <%s>' % (i + 1, ), remove_when_done=True): time.sleep(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/scrolling-task-name.py0000755000175100017510000000101313545407204026762 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A very simple progress bar where the name of the task scrolls, because it's too long. iterator. """ from __future__ import unicode_literals 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(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/simple-progress-bar.py0000755000175100017510000000055513545407204027017 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A very simple progress bar which keep track of the progress as we consume an iterator. """ from __future__ import unicode_literals import time from prompt_toolkit.shortcuts import ProgressBar def main(): with ProgressBar() as pb: for i in pb(range(800)): time.sleep(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/styled-1.py0000755000175100017510000000150613545407204024561 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A very simple progress bar which keep track of the progress as we consume an iterator. """ from __future__ import unicode_literals 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(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/styled-2.py0000755000175100017510000000246413545407204024566 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A very simple progress bar which keep track of the progress as we consume an iterator. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/progress-bar/styled-apt-get-install.py0000755000175100017510000000167313545407204027433 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Styled just like an apt-get installation. """ from __future__ import unicode_literals 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(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/styled-rainbow.py0000755000175100017510000000171513545407204026064 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple progress bar, visualised with rainbow colors (for fun). """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/progress-bar/styled-tqdm-1.py0000755000175100017510000000220313545407204025517 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Styled similar to tqdm, another progress bar implementation in Python. See: https://github.com/noamraph/tqdm """ from __future__ import unicode_literals 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(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/styled-tqdm-2.py0000755000175100017510000000207113545407204025523 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Styled similar to tqdm, another progress bar implementation in Python. See: https://github.com/noamraph/tqdm """ from __future__ import unicode_literals 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(.01) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/two-tasks.py0000755000175100017510000000170713545407204025056 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Two progress bars that run in parallel. """ from __future__ import unicode_literals 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(.05) def task_2(): for i in pb(range(150)): time.sleep(.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=.5) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/progress-bar/unknown-length.py0000755000175100017510000000105413545407204026073 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A very simple progress bar which keep track of the progress as we consume an iterator. """ from __future__ import unicode_literals 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. """ for i in range(1000): yield i def main(): with ProgressBar() as pb: for i in pb(data()): time.sleep(.1) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/prompts/0000755000175100017510000000000013545410361021634 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/prompts/accept-default.py0000644000175100017510000000077713545407204025104 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/asyncio-prompt.py0000755000175100017510000000430513545407204025201 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ (Python >= 3.5) 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.eventloop.defaults import use_asyncio_event_loop from prompt_toolkit.patch_stdout import patch_stdout from prompt_toolkit.shortcuts import PromptSession loop = asyncio.get_event_loop() async def print_counter(): """ Coroutine that prints counters. """ i = 0 while True: print('Counter: %i' % i) i += 1 await asyncio.sleep(3) async def interactive_shell(): """ Like `interactive_shell`, but doing things manual. """ # Create Prompt. session = PromptSession('Say something: ') # Patch stdout in something that will always print *above* the prompt when # something is written to stdout. # (This is optional, when `patch_stdout=True` has been given before.) # sys.stdout = prompt.app.stdout_proxy() # Run echo loop. Read text from stdin, and reply it back. while True: try: result = await session.prompt(async_=True) print('You said: "{0}"'.format(result)) except (EOFError, KeyboardInterrupt): return def main(): # Tell prompt_toolkit to use the asyncio event loop. use_asyncio_event_loop() with patch_stdout(): shell_task = asyncio.ensure_future(interactive_shell()) background_task = asyncio.gather(print_counter(), return_exceptions=True) loop.run_until_complete(shell_task) background_task.cancel() loop.run_until_complete(background_task) print('Quitting event loop. Bye.') if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/prompts/auto-completion/0000755000175100017510000000000013545410361024753 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/prompts/auto-completion/autocomplete-with-control-space.py0000755000175100017510000000223313545407204033553 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of using the control-space key binding for auto completion. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/autocompletion-like-readline.py0000755000175100017510000000160313545407204033077 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Autocompletion example that displays the autocompletions like readline does by binding a custom handler to the Tab key. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/autocompletion.py0000755000175100017510000000174013545407204030376 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/colored-completions-with-formatted-text.py0000755000175100017510000000666013545407204035241 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/colored-completions.py0000755000175100017510000000344713545407204031323 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of a custom completer class and the possibility of styling completions independently. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/combine-multiple-completers.py0000755000175100017510000000211413545407204032750 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of multiple individual completers that are combined into one. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/fuzzy-custom-completer.py0000755000175100017510000000254313545407204032025 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of a custom completer wrapped in a `FuzzyCompleter` for fuzzy matching. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/fuzzy-word-completer.py0000755000175100017510000000174113545407204031465 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/multi-column-autocompletion-with-meta.py0000755000175100017510000000205213545407204034713 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Autocompletion example that shows meta-information alongside the completions. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/multi-column-autocompletion.py0000755000175100017510000000154613545407204033025 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Similar to the autocompletion example. But display all the completions in multiple columns. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/auto-completion/slow-completions.py0000755000175100017510000000502413545407204030651 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals 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(.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() prompt_toolkit-2.0.10/examples/prompts/auto-suggestion.py0000755000175100017510000000274013545407204025353 0ustar jonathanjonathan00000000000000#!/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 __future__ import print_function, unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/autocorrection.py0000755000175100017510000000216113545407204025253 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/bottom-toolbar.py0000755000175100017510000000442213545407204025161 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals 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=.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() prompt_toolkit-2.0.10/examples/prompts/clock-input.py0000755000175100017510000000113713545407204024445 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a 'dynamic' prompt. On that shows the current time in the prompt. """ from __future__ import unicode_literals 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', '%s:%s:%s' % (now.hour, now.minute, now.second)), ('bg:cornsilk fg:maroon', ' Enter something: ') ] def main(): result = prompt(get_prompt, refresh_interval=.5) print('You said: %s' % result) if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/prompts/colored-prompt.py0000755000175100017510000000341213545407204025161 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a colored prompt. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/confirmation-prompt.py0000755000175100017510000000040013545407204026214 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a confirmation prompt. """ from __future__ import unicode_literals from prompt_toolkit.shortcuts import confirm if __name__ == '__main__': answer = confirm('Should we do that?') print('You said: %s' % answer) prompt_toolkit-2.0.10/examples/prompts/custom-key-binding.py0000755000175100017510000000352713545407204025732 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of adding a custom key binding to a prompt. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.application import 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) # 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() prompt_toolkit-2.0.10/examples/prompts/custom-lexer.py0000755000175100017510000000133113545407204024640 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ An example of a custom lexer that prints the input text in random colors. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/custom-vi-operator-and-text-object.py0000755000175100017510000000425513545407204030766 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/finalterm-shell-integration.py0000755000175100017510000000251113545407204027621 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Mark the start and end of the prompt with Final term (iterm2) escape sequences. See: https://iterm2.com/finalterm.html """ from __future__ import unicode_literals 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('{}Say something: # {}'.format(before, after))) # Output. sys.stdout.write(BEFORE_OUTPUT) print('You said: %s' % answer) sys.stdout.write(AFTER_OUTPUT.format(command_status=0)) prompt_toolkit-2.0.10/examples/prompts/get-input-vi-mode.py0000755000175100017510000000047413545407204025472 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/get-input-with-default.py0000755000175100017510000000057313545407204026527 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/get-input.py0000755000175100017510000000036413545407204024132 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ The most simple prompt example. """ from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) prompt_toolkit-2.0.10/examples/prompts/get-multiline-input.py0000755000175100017510000000203613545407204026130 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/get-password-with-toggle-display-shortcut.py0000755000175100017510000000151113545407204032374 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/get-password.py0000755000175100017510000000033013545407204024626 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': password = prompt('Password: ', is_password=True) print('You said: %s' % password) prompt_toolkit-2.0.10/examples/prompts/history/0000755000175100017510000000000013545410361023335 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/prompts/history/persistent-history.py0000755000175100017510000000140213545407204027610 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/history/slow-history.py0000755000175100017510000000260613545407204026403 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals 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 'item-%s' % (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() prompt_toolkit-2.0.10/examples/prompts/html-input.py0000755000175100017510000000070013545407204024311 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of a syntax-highlighted HTML input line. (This requires Pygments to be installed.) """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/input-validation.py0000755000175100017510000000144013545407204025501 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of input validation. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/inputhook.py0000755000175100017510000000502313545407204024233 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals from pygments.lexers.python import PythonLexer import gobject import gtk 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() prompt_toolkit-2.0.10/examples/prompts/mouse-support.py0000755000175100017510000000062713545407204025062 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/multiline-prompt.py0000755000175100017510000000047513545407204025542 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of how the input can be indented. """ from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/no-wrapping.py0000755000175100017510000000035613545407204024460 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ', wrap_lines=False, multiline=True) print('You said: %s' % answer) prompt_toolkit-2.0.10/examples/prompts/operate-and-get-next.py0000755000175100017510000000067513545407204026153 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals from prompt_toolkit.shortcuts import PromptSession def main(): session = PromptSession('prompt> ') while True: session.prompt() if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/prompts/patch-stdout.py0000755000175100017510000000170213545407204024632 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/regular-language.py0000755000175100017510000000577013545407204025446 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals 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 = ['sqrt', 'log', 'sin', 'ln'] 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)), }[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 prompt_toolkit-2.0.10/examples/prompts/rprompt.py0000755000175100017510000000277113545407204023725 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/swap-light-and-dark-colors.py0000755000175100017510000000357713545407204027264 0ustar jonathanjonathan00000000000000#!/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 __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/switch-between-vi-emacs.py0000755000175100017510000000206213545407204026645 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example that displays how to switch between Emacs and Vi input mode. """ from __future__ import unicode_literals 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() prompt_toolkit-2.0.10/examples/prompts/system-clipboard-integration.py0000755000175100017510000000121313545407204030012 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demonstration of a custom clipboard class. This requires the 'pyperclip' library to be installed. """ from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/system-prompt.py0000755000175100017510000000140613545407204025057 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/terminal-title.py0000755000175100017510000000047213545407204025150 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals 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) prompt_toolkit-2.0.10/examples/prompts/up-arrow-partial-string-matching.py0000755000175100017510000000237213545407204030521 0ustar jonathanjonathan00000000000000#!/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 __future__ import print_function, unicode_literals 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() prompt_toolkit-2.0.10/examples/telnet/0000755000175100017510000000000013545410361021423 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/telnet/chat-app.py0000755000175100017510000000457713545407204023514 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals import logging import random from prompt_toolkit.contrib.telnet.server import TelnetServer from prompt_toolkit.eventloop import From, get_event_loop from prompt_toolkit.formatted_text import HTML from prompt_toolkit.shortcuts import clear, prompt # 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'] def interact(connection): write = connection.send # 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 = yield From(prompt(message='Type your name: ', async_=True)) # 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 = yield From(prompt(message=prompt_msg, async_=True)) _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), ]) def main(): server = TelnetServer(interact=interact, port=2323) server.start() get_event_loop().run_forever() if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/telnet/dialog.py0000755000175100017510000000144113545407204023241 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a telnet application that displays a dialog window. """ from __future__ import unicode_literals import logging from prompt_toolkit.contrib.telnet.server import TelnetServer from prompt_toolkit.eventloop import From, get_event_loop from prompt_toolkit.shortcuts.dialogs import yes_no_dialog # Set up logging logging.basicConfig() logging.getLogger().setLevel(logging.INFO) def interact(connection): result = yield From(yes_no_dialog( title='Yes/no dialog demo', text='Press yes or no', async_=True)) connection.send('You said: {}\n'.format(result)) connection.send('Bye.\n') def main(): server = TelnetServer(interact=interact, port=2323) server.start() get_event_loop().run_forever() if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/telnet/hello-world-asyncio.py0000755000175100017510000000243313545407204025677 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ A simple Telnet application that asks for input and responds. The interaction function is an asyncio coroutine. WARNING: This is experimental! Prompt_toolkit TaskLocals don't work together with asyncio coroutines. This is also why we have to specify the output and input manually. """ from __future__ import unicode_literals import asyncio import logging from prompt_toolkit.contrib.telnet.server import TelnetServer from prompt_toolkit.eventloop.defaults import use_asyncio_event_loop from prompt_toolkit.shortcuts import PromptSession # Set up logging logging.basicConfig() logging.getLogger().setLevel(logging.INFO) # Tell prompt_toolkit to use the asyncio event loop. use_asyncio_event_loop() async def interact(connection): session = PromptSession(output=connection.vt100_output, input=connection.vt100_input) connection.erase_screen() connection.send('Welcome!\n') # Ask for input. result = await session.prompt(message='Say something: ', async_=True) # Send output. connection.send('You said: {}\n'.format(result)) connection.send('Bye.\n') def main(): server = TelnetServer(interact=interact, port=2323) server.start() asyncio.get_event_loop().run_forever() if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/telnet/hello-world.py0000755000175100017510000000202413545407204024230 0ustar jonathanjonathan00000000000000#!/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. """ from __future__ import unicode_literals import logging from prompt_toolkit.contrib.telnet.server import TelnetServer from prompt_toolkit.eventloop import From, get_event_loop from prompt_toolkit.shortcuts import clear, prompt # Set up logging logging.basicConfig() logging.getLogger().setLevel(logging.INFO) def interact(connection): clear() connection.send('Welcome!\n') # Ask for input. result = yield From(prompt(message='Say something: ', async_=True)) # Send output. connection.send('You said: {}\n'.format(result)) connection.send('Bye.\n') def main(): server = TelnetServer(interact=interact, port=2323) server.start() get_event_loop().run_forever() if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/telnet/toolbar.py0000755000175100017510000000225113545407204023444 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a telnet application that displays a bottom toolbar and completions in the prompt. """ from __future__ import unicode_literals import logging from prompt_toolkit.completion import WordCompleter from prompt_toolkit.contrib.telnet.server import TelnetServer from prompt_toolkit.eventloop import From, get_event_loop from prompt_toolkit.shortcuts import prompt # Set up logging logging.basicConfig() logging.getLogger().setLevel(logging.INFO) 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...' result = yield From(prompt( 'Say something: ', bottom_toolbar=get_toolbar, completer=animal_completer, async_=True)) connection.send('You said: {}\n'.format(result)) connection.send('Bye.\n') def main(): server = TelnetServer(interact=interact, port=2323) server.start() get_event_loop().run_forever() if __name__ == '__main__': main() prompt_toolkit-2.0.10/examples/tutorial/0000755000175100017510000000000013545410361021773 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/examples/tutorial/sqlite-cli.py0000755000175100017510000000520313545407204024420 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import print_function, unicode_literals 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) prompt_toolkit-2.0.10/prompt_toolkit/0000755000175100017510000000000013545410361021400 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/__init__.py0000644000175100017510000000162013545410034023505 0ustar jonathanjonathan00000000000000""" prompt_toolkit ============== Author: Jonathan Slenders Description: prompt_toolkit is a Library for building powerful interactive command lines in Python. It can be a replacement for GNU Readline, but it can be much more than that. See the examples directory to learn about the usage. Probably, to get started, you might also want to have a look at `prompt_toolkit.shortcuts.prompt`. """ from __future__ import unicode_literals from .application import Application from .formatted_text import ANSI, HTML from .shortcuts import PromptSession, print_formatted_text, prompt # Don't forget to update in `docs/conf.py`! __version__ = '2.0.10' # Version tuple. VERSION = tuple(__version__.split('.')) __all__ = [ # Application. 'Application', # Shortcuts. 'prompt', 'PromptSession', 'print_formatted_text', # Formatted text. 'HTML', 'ANSI', ] prompt_toolkit-2.0.10/prompt_toolkit/application/0000755000175100017510000000000013545410361023703 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/application/__init__.py0000644000175100017510000000076413545407204026025 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .application import Application from .current import NoRunningApplicationError, get_app, set_app from .dummy import DummyApplication from .run_in_terminal import run_coroutine_in_terminal, run_in_terminal __all__ = [ # Application. 'Application', # Current. 'get_app', 'set_app', 'NoRunningApplicationError', # Dummy. 'DummyApplication', # Run_in_terminal 'run_coroutine_in_terminal', 'run_in_terminal', ] prompt_toolkit-2.0.10/prompt_toolkit/application/application.py0000644000175100017510000011324013545407204026563 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import os import re import signal import sys import time from subprocess import Popen from traceback import format_tb import six from prompt_toolkit.buffer import Buffer from prompt_toolkit.cache import SimpleCache from prompt_toolkit.clipboard import Clipboard, InMemoryClipboard from prompt_toolkit.enums import EditingMode from prompt_toolkit.eventloop import ( From, Return, call_from_executor, ensure_future, get_event_loop, run_in_executor, run_until_complete, ) from prompt_toolkit.eventloop.base import get_traceback_from_context from prompt_toolkit.filters import Condition, to_filter from prompt_toolkit.input.base import Input from prompt_toolkit.input.defaults import get_default_input from prompt_toolkit.input.typeahead import get_typeahead, store_typeahead from prompt_toolkit.key_binding.bindings.page_navigation import ( load_page_navigation_bindings, ) from prompt_toolkit.key_binding.defaults import load_key_bindings from prompt_toolkit.key_binding.emacs_state import EmacsState from prompt_toolkit.key_binding.key_bindings import ( ConditionalKeyBindings, GlobalOnlyKeyBindings, KeyBindings, KeyBindingsBase, merge_key_bindings, ) from prompt_toolkit.key_binding.key_processor import KeyProcessor from prompt_toolkit.key_binding.vi_state import ViState from prompt_toolkit.keys import Keys from prompt_toolkit.layout.controls import BufferControl from prompt_toolkit.layout.dummy import create_dummy_layout from prompt_toolkit.layout.layout import Layout, walk from prompt_toolkit.output import ColorDepth, Output from prompt_toolkit.output.defaults import get_default_output from prompt_toolkit.renderer import Renderer, print_formatted_text from prompt_toolkit.search import SearchState from prompt_toolkit.styles import ( BaseStyle, DummyStyle, DummyStyleTransformation, DynamicStyle, StyleTransformation, default_pygments_style, default_ui_style, merge_styles, ) from prompt_toolkit.utils import Event, in_main_thread from .current import set_app from .run_in_terminal import run_coroutine_in_terminal, run_in_terminal __all__ = [ 'Application', ] class Application(object): """ The main Application class! This glues everything together. :param layout: A :class:`~prompt_toolkit.layout.Layout` instance. :param key_bindings: :class:`~prompt_toolkit.key_binding.KeyBindingsBase` instance for the key bindings. :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` to use. :param on_abort: What to do when Control-C is pressed. :param on_exit: What to do when Control-D is pressed. :param full_screen: When True, run the application on the alternate screen buffer. :param color_depth: Any :class:`~.ColorDepth` value, a callable that returns a :class:`~.ColorDepth` or `None` for default. :param erase_when_done: (bool) Clear the application output when it finishes. :param reverse_vi_search_direction: Normally, in Vi mode, a '/' searches forward and a '?' searches backward. In Readline mode, this is usually reversed. :param min_redraw_interval: Number of seconds to wait between redraws. Use this for applications where `invalidate` is called a lot. This could cause a lot of terminal output, which some terminals are not able to process. `None` means that every `invalidate` will be scheduled right away (which is usually fine). When one `invalidate` is called, but a scheduled redraw of a previous `invalidate` call has not been executed yet, nothing will happen in any case. :param max_render_postpone_time: When there is high CPU (a lot of other scheduled calls), postpone the rendering max x seconds. '0' means: don't postpone. '.5' means: try to draw at least twice a second. Filters: :param mouse_support: (:class:`~prompt_toolkit.filters.Filter` or boolean). When True, enable mouse support. :param paste_mode: :class:`~prompt_toolkit.filters.Filter` or boolean. :param editing_mode: :class:`~prompt_toolkit.enums.EditingMode`. :param enable_page_navigation_bindings: When `True`, enable the page navigation key bindings. These include both Emacs and Vi bindings like page-up, page-down and so on to scroll through pages. Mostly useful for creating an editor or other full screen applications. Probably, you don't want this for the implementation of a REPL. By default, this is enabled if `full_screen` is set. Callbacks (all of these should accept a :class:`~prompt_toolkit.application.Application` object as input.) :param on_reset: Called during reset. :param on_invalidate: Called when the UI has been invalidated. :param before_render: Called right before rendering. :param after_render: Called right after rendering. I/O: :param input: :class:`~prompt_toolkit.input.Input` instance. :param output: :class:`~prompt_toolkit.output.Output` instance. (Probably Vt100_Output or Win32Output.) Usage: app = Application(...) app.run() """ def __init__(self, layout=None, style=None, include_default_pygments_style=True, style_transformation=None, key_bindings=None, clipboard=None, full_screen=False, color_depth=None, mouse_support=False, enable_page_navigation_bindings=None, # Can be None, True or False. paste_mode=False, editing_mode=EditingMode.EMACS, erase_when_done=False, reverse_vi_search_direction=False, min_redraw_interval=None, max_render_postpone_time=0, on_reset=None, on_invalidate=None, before_render=None, after_render=None, # I/O. input=None, output=None): # If `enable_page_navigation_bindings` is not specified, enable it in # case of full screen applications only. This can be overridden by the user. if enable_page_navigation_bindings is None: enable_page_navigation_bindings = Condition(lambda: self.full_screen) paste_mode = to_filter(paste_mode) mouse_support = to_filter(mouse_support) reverse_vi_search_direction = to_filter(reverse_vi_search_direction) enable_page_navigation_bindings = to_filter(enable_page_navigation_bindings) include_default_pygments_style = to_filter(include_default_pygments_style) assert layout is None or isinstance(layout, Layout), 'Got layout: %r' % (layout, ) assert key_bindings is None or isinstance(key_bindings, KeyBindingsBase) assert clipboard is None or isinstance(clipboard, Clipboard) assert isinstance(full_screen, bool) assert (color_depth is None or callable(color_depth) or color_depth in ColorDepth._ALL), 'Got color_depth: %r' % (color_depth, ) assert isinstance(editing_mode, six.string_types) assert style is None or isinstance(style, BaseStyle) assert style_transformation is None or isinstance(style_transformation, StyleTransformation) assert isinstance(erase_when_done, bool) assert min_redraw_interval is None or isinstance(min_redraw_interval, (float, int)) assert max_render_postpone_time is None or isinstance(max_render_postpone_time, (float, int)) assert on_reset is None or callable(on_reset) assert on_invalidate is None or callable(on_invalidate) assert before_render is None or callable(before_render) assert after_render is None or callable(after_render) assert output is None or isinstance(output, Output) assert input is None or isinstance(input, Input) if layout is None: layout = create_dummy_layout() if style_transformation is None: style_transformation = DummyStyleTransformation() self.style = style self.style_transformation = style_transformation # Key bindings. self.key_bindings = key_bindings self._default_bindings = load_key_bindings() self._page_navigation_bindings = load_page_navigation_bindings() self.layout = layout self.clipboard = clipboard or InMemoryClipboard() self.full_screen = full_screen self._color_depth = color_depth self.mouse_support = mouse_support self.paste_mode = paste_mode self.editing_mode = editing_mode self.erase_when_done = erase_when_done self.reverse_vi_search_direction = reverse_vi_search_direction self.enable_page_navigation_bindings = enable_page_navigation_bindings self.min_redraw_interval = min_redraw_interval self.max_render_postpone_time = max_render_postpone_time # Events. self.on_invalidate = Event(self, on_invalidate) self.on_reset = Event(self, on_reset) self.before_render = Event(self, before_render) self.after_render = Event(self, after_render) # I/O. self.output = output or get_default_output() self.input = input or get_default_input() # List of 'extra' functions to execute before a Application.run. self.pre_run_callables = [] self._is_running = False self.future = None #: Quoted insert. This flag is set if we go into quoted insert mode. self.quoted_insert = False #: Vi state. (For Vi key bindings.) self.vi_state = ViState() self.emacs_state = EmacsState() #: When to flush the input (For flushing escape keys.) This is important #: on terminals that use vt100 input. We can't distinguish the escape #: key from for instance the left-arrow key, if we don't know what follows #: after "\x1b". This little timer will consider "\x1b" to be escape if #: nothing did follow in this time span. #: This seems to work like the `ttimeoutlen` option in Vim. self.ttimeoutlen = .5 # Seconds. #: Like Vim's `timeoutlen` option. This can be `None` or a float. For #: instance, suppose that we have a key binding AB and a second key #: binding A. If the uses presses A and then waits, we don't handle #: this binding yet (unless it was marked 'eager'), because we don't #: know what will follow. This timeout is the maximum amount of time #: that we wait until we call the handlers anyway. Pass `None` to #: disable this timeout. self.timeoutlen = 1.0 #: The `Renderer` instance. # Make sure that the same stdout is used, when a custom renderer has been passed. self._merged_style = self._create_merged_style(include_default_pygments_style) self.renderer = Renderer( self._merged_style, self.output, self.input, full_screen=full_screen, mouse_support=mouse_support, cpr_not_supported_callback=self.cpr_not_supported_callback) #: Render counter. This one is increased every time the UI is rendered. #: It can be used as a key for caching certain information during one #: rendering. self.render_counter = 0 # Invalidate flag. When 'True', a repaint has been scheduled. self._invalidated = False self._invalidate_events = [] # Collection of 'invalidate' Event objects. self._last_redraw_time = 0 # Unix timestamp of last redraw. Used when # `min_redraw_interval` is given. #: The `InputProcessor` instance. self.key_processor = KeyProcessor(_CombinedRegistry(self)) # If `run_in_terminal` was called. This will point to a `Future` what will be # set at the point when the previous run finishes. self._running_in_terminal = False self._running_in_terminal_f = None # Trigger initialize callback. self.reset() def _create_merged_style(self, include_default_pygments_style): """ Create a `Style` object that merges the default UI style, the default pygments style, and the custom user style. """ dummy_style = DummyStyle() pygments_style = default_pygments_style() @DynamicStyle def conditional_pygments_style(): if include_default_pygments_style(): return pygments_style else: return dummy_style return merge_styles([ default_ui_style(), conditional_pygments_style, DynamicStyle(lambda: self.style), ]) @property def color_depth(self): """ Active :class:`.ColorDepth`. """ depth = self._color_depth if callable(depth): depth = depth() if depth is None: depth = ColorDepth.default() return depth @property def current_buffer(self): """ The currently focused :class:`~.Buffer`. (This returns a dummy :class:`.Buffer` when none of the actual buffers has the focus. In this case, it's really not practical to check for `None` values or catch exceptions every time.) """ return self.layout.current_buffer or Buffer(name='dummy-buffer') # Dummy buffer. @property def current_search_state(self): """ Return the current :class:`.SearchState`. (The one for the focused :class:`.BufferControl`.) """ ui_control = self.layout.current_control if isinstance(ui_control, BufferControl): return ui_control.search_state else: return SearchState() # Dummy search state. (Don't return None!) def reset(self): """ Reset everything, for reading the next input. """ # Notice that we don't reset the buffers. (This happens just before # returning, and when we have multiple buffers, we clearly want the # content in the other buffers to remain unchanged between several # calls of `run`. (And the same is true for the focus stack.) self.exit_style = '' self.renderer.reset() self.key_processor.reset() self.layout.reset() self.vi_state.reset() self.emacs_state.reset() # Trigger reset event. self.on_reset.fire() # Make sure that we have a 'focusable' widget focused. # (The `Layout` class can't determine this.) layout = self.layout if not layout.current_control.is_focusable(): for w in layout.find_all_windows(): if w.content.is_focusable(): layout.current_window = w break def invalidate(self): """ Thread safe way of sending a repaint trigger to the input event loop. """ # Never schedule a second redraw, when a previous one has not yet been # executed. (This should protect against other threads calling # 'invalidate' many times, resulting in 100% CPU.) if self._invalidated: return else: self._invalidated = True # Trigger event. self.on_invalidate.fire() def redraw(): self._invalidated = False self._redraw() def schedule_redraw(): # Call redraw in the eventloop (thread safe). # Usually with the high priority, in order to make the application # feel responsive, but this can be tuned by changing the value of # `max_render_postpone_time`. if self.max_render_postpone_time: _max_postpone_until = time.time() + self.max_render_postpone_time else: _max_postpone_until = None call_from_executor( redraw, _max_postpone_until=_max_postpone_until) if self.min_redraw_interval: # When a minimum redraw interval is set, wait minimum this amount # of time between redraws. diff = time.time() - self._last_redraw_time if diff < self.min_redraw_interval: def redraw_in_future(): time.sleep(self.min_redraw_interval - diff) schedule_redraw() run_in_executor(redraw_in_future) else: schedule_redraw() else: schedule_redraw() @property def invalidated(self): " True when a redraw operation has been scheduled. " return self._invalidated def _redraw(self, render_as_done=False): """ Render the command line again. (Not thread safe!) (From other threads, or if unsure, use :meth:`.Application.invalidate`.) :param render_as_done: make sure to put the cursor after the UI. """ # Only draw when no sub application was started. if self._is_running and not self._running_in_terminal: if self.min_redraw_interval: self._last_redraw_time = time.time() # Clear the 'rendered_ui_controls' list. (The `Window` class will # populate this during the next rendering.) self.rendered_user_controls = [] # Render self.render_counter += 1 self.before_render.fire() # NOTE: We want to make sure this Application is the active one, if # we have a situation with multiple concurrent running apps. # We had the case with pymux where `invalidate()` was called # at the point where another Application was active. This # would cause prompt_toolkit to render the wrong application # to this output device. with set_app(self): if render_as_done: if self.erase_when_done: self.renderer.erase() else: # Draw in 'done' state and reset renderer. self.renderer.render(self, self.layout, is_done=render_as_done) else: self.renderer.render(self, self.layout) self.layout.update_parents_relations() # Fire render event. self.after_render.fire() self._update_invalidate_events() def _update_invalidate_events(self): """ Make sure to attach 'invalidate' handlers to all invalidate events in the UI. """ # Remove all the original event handlers. (Components can be removed # from the UI.) for ev in self._invalidate_events: ev -= self._invalidate_handler # Gather all new events. # (All controls are able to invalidate themselves.) def gather_events(): for c in self.layout.find_all_controls(): for ev in c.get_invalidate_events(): yield ev self._invalidate_events = list(gather_events()) for ev in self._invalidate_events: ev += self._invalidate_handler def _invalidate_handler(self, sender): """ Handler for invalidate events coming from UIControls. (This handles the difference in signature between event handler and `self.invalidate`. It also needs to be a method -not a nested function-, so that we can remove it again .) """ self.invalidate() def _on_resize(self): """ When the window size changes, we erase the current output and request again the cursor position. When the CPR answer arrives, the output is drawn again. """ # Erase, request position (when cursor is at the start position) # and redraw again. -- The order is important. self.renderer.erase(leave_alternate_screen=False) self._request_absolute_cursor_position() self._redraw() def _pre_run(self, pre_run=None): " Called during `run`. " if pre_run: pre_run() # Process registered "pre_run_callables" and clear list. for c in self.pre_run_callables: c() del self.pre_run_callables[:] def run_async(self, pre_run=None): """ Run asynchronous. Return a prompt_toolkit :class:`~prompt_toolkit.eventloop.Future` object. If you wish to run on top of asyncio, remember that a prompt_toolkit `Future` needs to be converted to an asyncio `Future`. The cleanest way is to call :meth:`~prompt_toolkit.eventloop.Future.to_asyncio_future`. Also make sure to tell prompt_toolkit to use the asyncio event loop. .. code:: python from prompt_toolkit.eventloop import use_asyncio_event_loop from asyncio import get_event_loop use_asyncio_event_loop() get_event_loop().run_until_complete( application.run_async().to_asyncio_future()) """ assert not self._is_running, 'Application is already running.' def _run_async(): " Coroutine. " loop = get_event_loop() f = loop.create_future() self.future = f # XXX: make sure to set this before calling '_redraw'. # Counter for cancelling 'flush' timeouts. Every time when a key is # pressed, we start a 'flush' timer for flushing our escape key. But # when any subsequent input is received, a new timer is started and # the current timer will be ignored. flush_counter = [0] # Non local. # Reset. self.reset() self._pre_run(pre_run) # Feed type ahead input first. self.key_processor.feed_multiple(get_typeahead(self.input)) self.key_processor.process_keys() def read_from_input(): # Ignore when we aren't running anymore. This callback will # removed from the loop next time. (It could be that it was # still in the 'tasks' list of the loop.) # Except: if we need to process incoming CPRs. if not self._is_running and not self.renderer.waiting_for_cpr: return # Get keys from the input object. keys = self.input.read_keys() # Feed to key processor. self.key_processor.feed_multiple(keys) self.key_processor.process_keys() # Quit when the input stream was closed. if self.input.closed: f.set_exception(EOFError) else: # Increase this flush counter. flush_counter[0] += 1 counter = flush_counter[0] # Automatically flush keys. # (_daemon needs to be set, otherwise, this will hang the # application for .5 seconds before exiting.) run_in_executor( lambda: auto_flush_input(counter), _daemon=True) def auto_flush_input(counter): # Flush input after timeout. # (Used for flushing the enter key.) time.sleep(self.ttimeoutlen) if flush_counter[0] == counter: call_from_executor(flush_input) def flush_input(): if not self.is_done: # Get keys, and feed to key processor. keys = self.input.flush_keys() self.key_processor.feed_multiple(keys) self.key_processor.process_keys() if self.input.closed: f.set_exception(EOFError) # Enter raw mode. with self.input.raw_mode(): with self.input.attach(read_from_input): # Draw UI. self._request_absolute_cursor_position() self._redraw() has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread() if has_sigwinch: previous_winch_handler = loop.add_signal_handler( signal.SIGWINCH, self._on_resize) # Wait for UI to finish. try: result = yield From(f) finally: # In any case, when the application finishes. (Successful, # or because of an error.) try: self._redraw(render_as_done=True) finally: # _redraw has a good chance to fail if it calls widgets # with bad code. Make sure to reset the renderer anyway. self.renderer.reset() # Unset `is_running`, this ensures that possibly # scheduled draws won't paint during the following # yield. self._is_running = False # Detach event handlers for invalidate events. # (Important when a UIControl is embedded in # multiple applications, like ptterm in pymux. An # invalidate should not trigger a repaint in # terminated applications.) for ev in self._invalidate_events: ev -= self._invalidate_handler self._invalidate_events = [] # Wait for CPR responses. if self.input.responds_to_cpr: yield From(self.renderer.wait_for_cpr_responses()) if has_sigwinch: loop.add_signal_handler(signal.SIGWINCH, previous_winch_handler) # Wait for the run-in-terminals to terminate. previous_run_in_terminal_f = self._running_in_terminal_f if previous_run_in_terminal_f: yield From(previous_run_in_terminal_f) # Store unprocessed input as typeahead for next time. store_typeahead(self.input, self.key_processor.empty_queue()) raise Return(result) def _run_async2(): self._is_running = True with set_app(self): try: f = From(_run_async()) result = yield f finally: # Set the `_is_running` flag to `False`. Normally this # happened already in the finally block in `run_async` # above, but in case of exceptions, that's not always the # case. self._is_running = False raise Return(result) return ensure_future(_run_async2()) def run(self, pre_run=None, set_exception_handler=True, inputhook=None): """ A blocking 'run' call that waits until the UI is finished. :param set_exception_handler: When set, in case of an exception, go out of the alternate screen and hide the application, display the exception, and wait for the user to press ENTER. :param inputhook: None or a callable that takes an `InputHookContext`. """ loop = get_event_loop() def run(): f = self.run_async(pre_run=pre_run) run_until_complete(f, inputhook=inputhook) return f.result() def handle_exception(context): " Print the exception, using run_in_terminal. " # For Python 2: we have to get traceback at this point, because # we're still in the 'except:' block of the event loop where the # traceback is still available. Moving this code in the # 'print_exception' coroutine will loose the exception. tb = get_traceback_from_context(context) formatted_tb = ''.join(format_tb(tb)) def print_exception(): # Print output. Similar to 'loop.default_exception_handler', # but don't use logger. (This works better on Python 2.) print('\nUnhandled exception in event loop:') print(formatted_tb) print('Exception %s' % (context.get('exception'), )) yield From(_do_wait_for_enter('Press ENTER to continue...')) run_coroutine_in_terminal(print_exception) if set_exception_handler: # Run with patched exception handler. previous_exc_handler = loop.get_exception_handler() loop.set_exception_handler(handle_exception) try: return run() finally: loop.set_exception_handler(previous_exc_handler) else: run() def cpr_not_supported_callback(self): """ Called when we don't receive the cursor position response in time. """ if not self.input.responds_to_cpr: return # We know about this already. def in_terminal(): self.output.write( "WARNING: your terminal doesn't support cursor position requests (CPR).\r\n") self.output.flush() run_in_terminal(in_terminal) def exit(self, result=None, exception=None, style=''): """ Exit application. :param result: Set this result for the application. :param exception: Set this exception as the result for an application. For a prompt, this is often `EOFError` or `KeyboardInterrupt`. :param style: Apply this style on the whole content when quitting, often this is 'class:exiting' for a prompt. (Used when `erase_when_done` is not set.) """ assert result is None or exception is None if self.future is None: raise Exception( 'Application is not running. Application.exit() failed.') if self.future.done(): raise Exception( 'Return value already set. Application.exit() failed.') self.exit_style = style if exception is not None: self.future.set_exception(exception) else: self.future.set_result(result) def _request_absolute_cursor_position(self): """ Send CPR request. """ # Note: only do this if the input queue is not empty, and a return # value has not been set. Otherwise, we won't be able to read the # response anyway. if not self.key_processor.input_queue and not self.is_done: self.renderer.request_absolute_cursor_position() def run_system_command(self, command, wait_for_enter=True, display_before_text='', wait_text='Press ENTER to continue...'): """ Run system command (While hiding the prompt. When finished, all the output will scroll above the prompt.) :param command: Shell command to be executed. :param wait_for_enter: FWait for the user to press enter, when the command is finished. :param display_before_text: If given, text to be displayed before the command executes. :return: A `Future` object. """ assert isinstance(wait_for_enter, bool) def run(): # Try to use the same input/output file descriptors as the one, # used to run this application. try: input_fd = self.input.fileno() except AttributeError: input_fd = sys.stdin.fileno() try: output_fd = self.output.fileno() except AttributeError: output_fd = sys.stdout.fileno() # Run sub process. def run_command(): self.print_text(display_before_text) p = Popen(command, shell=True, stdin=input_fd, stdout=output_fd) p.wait() yield run_in_executor(run_command) # Wait for the user to press enter. if wait_for_enter: yield From(_do_wait_for_enter(wait_text)) return run_coroutine_in_terminal(run) def suspend_to_background(self, suspend_group=True): """ (Not thread safe -- to be called from inside the key bindings.) Suspend process. :param suspend_group: When true, suspend the whole process group. (This is the default, and probably what you want.) """ # Only suspend when the operating system supports it. # (Not on Windows.) if hasattr(signal, 'SIGTSTP'): def run(): # Send `SIGSTP` to own process. # This will cause it to suspend. # Usually we want the whole process group to be suspended. This # handles the case when input is piped from another process. if suspend_group: os.kill(0, signal.SIGTSTP) else: os.kill(os.getpid(), signal.SIGTSTP) run_in_terminal(run) def print_text(self, text, style=None): """ Print a list of (style_str, text) tuples to the output. (When the UI is running, this method has to be called through `run_in_terminal`, otherwise it will destroy the UI.) :param text: List of ``(style_str, text)`` tuples. :param style: Style class to use. Defaults to the active style in the CLI. """ print_formatted_text( output=self.output, formatted_text=text, style=style or self._merged_style, color_depth=self.color_depth, style_transformation=self.style_transformation) @property def is_running(self): " `True` when the application is currently active/running. " return self._is_running @property def is_done(self): return self.future and self.future.done() def get_used_style_strings(self): """ Return a list of used style strings. This is helpful for debugging, and for writing a new `Style`. """ return sorted([ re.sub(r'\s+', ' ', style_str).strip() for style_str in self.renderer._attrs_for_style.keys()]) class _CombinedRegistry(KeyBindingsBase): """ The `KeyBindings` of key bindings for a `Application`. This merges the global key bindings with the one of the current user control. """ def __init__(self, app): self.app = app self._cache = SimpleCache() @property def _version(self): """ Not needed - this object is not going to be wrapped in another KeyBindings object. """ raise NotImplementedError def _create_key_bindings(self, current_window, other_controls): """ Create a `KeyBindings` object that merges the `KeyBindings` from the `UIControl` with all the parent controls and the global key bindings. """ key_bindings = [] collected_containers = set() # Collect key bindings from currently focused control and all parent # controls. Don't include key bindings of container parent controls. container = current_window while True: collected_containers.add(container) kb = container.get_key_bindings() if kb is not None: key_bindings.append(kb) if container.is_modal(): break parent = self.app.layout.get_parent(container) if parent is None: break else: container = parent # Include global bindings (starting at the top-model container). for c in walk(container): if c not in collected_containers: kb = c.get_key_bindings() if kb is not None: key_bindings.append(GlobalOnlyKeyBindings(kb)) # Add App key bindings if self.app.key_bindings: key_bindings.append(self.app.key_bindings) # Add mouse bindings. key_bindings.append(ConditionalKeyBindings( self.app._page_navigation_bindings, self.app.enable_page_navigation_bindings)) key_bindings.append(self.app._default_bindings) # Reverse this list. The current control's key bindings should come # last. They need priority. key_bindings = key_bindings[::-1] return merge_key_bindings(key_bindings) @property def _key_bindings(self): current_window = self.app.layout.current_window other_controls = list(self.app.layout.find_all_controls()) key = current_window, frozenset(other_controls) return self._cache.get( key, lambda: self._create_key_bindings(current_window, other_controls)) def get_bindings_for_keys(self, keys): return self._key_bindings.get_bindings_for_keys(keys) def get_bindings_starting_with_keys(self, keys): return self._key_bindings.get_bindings_starting_with_keys(keys) def _do_wait_for_enter(wait_text): """ Create a sub application to wait for the enter key press. This has two advantages over using 'input'/'raw_input': - This will share the same input/output I/O. - This doesn't block the event loop. """ from prompt_toolkit.shortcuts import PromptSession key_bindings = KeyBindings() @key_bindings.add('enter') def _(event): event.app.exit() @key_bindings.add(Keys.Any) def _(event): " Disallow typing. " pass session = PromptSession( message=wait_text, key_bindings=key_bindings) yield From(session.app.run_async()) prompt_toolkit-2.0.10/prompt_toolkit/application/current.py0000644000175100017510000000452113545407204025743 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from contextlib import contextmanager from prompt_toolkit.eventloop.context import TaskLocal, TaskLocalNotSetError __all__ = [ 'get_app', 'set_app', 'NoRunningApplicationError', ] _current_app = TaskLocal() def get_app(raise_exception=False, return_none=False): """ Get the current active (running) Application. An :class:`.Application` is active during the :meth:`.Application.run_async` call. We assume that there can only be one :class:`.Application` active at the same time. There is only one terminal window, with only one stdin and stdout. This makes the code significantly easier than passing around the :class:`.Application` everywhere. If no :class:`.Application` is running, then return by default a :class:`.DummyApplication`. For practical reasons, we prefer to not raise an exception. This way, we don't have to check all over the place whether an actual `Application` was returned. (For applications like pymux where we can have more than one `Application`, we'll use a work-around to handle that.) :param raise_exception: When `True`, raise :class:`.NoRunningApplicationError` instead of returning a :class:`.DummyApplication` if no application is running. :param return_none: When `True`, return `None` instead of returning a :class:`.DummyApplication` if no application is running. """ try: value = _current_app.get() except TaskLocalNotSetError: if raise_exception: raise NoRunningApplicationError elif return_none: return None else: from .dummy import DummyApplication return DummyApplication() else: return value @contextmanager def set_app(app): """ Context manager that sets the given :class:`.Application` active. (Usually, not needed to call outside of prompt_toolkit.) """ from .application import Application assert app is None or isinstance(app, Application) previous = get_app(return_none=True) _current_app.set(app) try: yield finally: if previous: _current_app.set(previous) else: _current_app.delete() class NoRunningApplicationError(Exception): " There is no active application right now. " prompt_toolkit-2.0.10/prompt_toolkit/application/dummy.py0000644000175100017510000000152713545407204025417 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.input import DummyInput from prompt_toolkit.output import DummyOutput from .application import Application __all__ = [ 'DummyApplication', ] class DummyApplication(Application): """ When no :class:`.Application` is running, :func:`.get_app` will run an instance of this :class:`.DummyApplication` instead. """ def __init__(self): super(DummyApplication, self).__init__(output=DummyOutput(), input=DummyInput()) def run(self): raise NotImplementedError('A DummyApplication is not supposed to run.') def run_async(self): raise NotImplementedError('A DummyApplication is not supposed to run.') def run_system_command(self): raise NotImplementedError def suspend_to_background(self): raise NotImplementedError prompt_toolkit-2.0.10/prompt_toolkit/application/run_in_terminal.py0000644000175100017510000000730413545407204027450 0ustar jonathanjonathan00000000000000""" Tools for running functions on the terminal above the current application or prompt. """ from __future__ import unicode_literals from prompt_toolkit.eventloop import ( From, Future, Return, ensure_future, get_event_loop, run_in_executor, ) from .current import get_app __all__ = [ 'run_in_terminal', 'run_coroutine_in_terminal', ] def run_in_terminal(func, render_cli_done=False, in_executor=False): """ Run function on the terminal above the current application or prompt. What this does is first hiding the prompt, then running this callable (which can safely output to the terminal), and then again rendering the prompt which causes the output of this function to scroll above the prompt. :param func: The callable to execute. :param render_cli_done: When True, render the interface in the 'Done' state first, then execute the function. If False, erase the interface first. :param in_executor: When True, run in executor. (Use this for long blocking functions, when you don't want to block the event loop.) :returns: A `Future`. """ if in_executor: def async_func(): f = run_in_executor(func) return f else: def async_func(): result = func() return Future.succeed(result) return run_coroutine_in_terminal(async_func, render_cli_done=render_cli_done) def run_coroutine_in_terminal(async_func, render_cli_done=False): """ Suspend the current application and run this coroutine instead. `async_func` can be a coroutine or a function that returns a Future. :param async_func: A function that returns either a Future or coroutine when called. :returns: A `Future`. """ assert callable(async_func) loop = get_event_loop() # Make sure to run this function in the current `Application`, or if no # application is active, run it normally. app = get_app(return_none=True) if app is None: return ensure_future(async_func()) assert app._is_running # When a previous `run_in_terminal` call was in progress. Wait for that # to finish, before starting this one. Chain to previous call. previous_run_in_terminal_f = app._running_in_terminal_f new_run_in_terminal_f = loop.create_future() app._running_in_terminal_f = new_run_in_terminal_f def _run_in_t(): " Coroutine. " # Wait for the previous `run_in_terminal` to finish. if previous_run_in_terminal_f is not None: yield previous_run_in_terminal_f # Wait for all CPRs to arrive. We don't want to detach the input until # all cursor position responses have been arrived. Otherwise, the tty # will echo its input and can show stuff like ^[[39;1R. if app.input.responds_to_cpr: yield From(app.renderer.wait_for_cpr_responses()) # Draw interface in 'done' state, or erase. if render_cli_done: app._redraw(render_as_done=True) else: app.renderer.erase() # Disable rendering. app._running_in_terminal = True # Detach input. try: with app.input.detach(): with app.input.cooked_mode(): result = yield From(async_func()) raise Return(result) # Same as: "return result" finally: # Redraw interface again. try: app._running_in_terminal = False app.renderer.reset() app._request_absolute_cursor_position() app._redraw() finally: new_run_in_terminal_f.set_result(None) return ensure_future(_run_in_t()) prompt_toolkit-2.0.10/prompt_toolkit/auto_suggest.py0000644000175100017510000001236213545407204024471 0ustar jonathanjonathan00000000000000""" `Fish-style `_ like auto-suggestion. While a user types input in a certain buffer, suggestions are generated (asynchronously.) Usually, they are displayed after the input. When the cursor presses the right arrow and the cursor is at the end of the input, the suggestion will be inserted. If you want the auto suggestions to be asynchronous (in a background thread), because they take too much time, and could potentially block the event loop, then wrap the :class:`.AutoSuggest` instance into a :class:`.ThreadedAutoSuggest`. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from .eventloop import Future, run_in_executor from .filters import to_filter __all__ = [ 'Suggestion', 'AutoSuggest', 'ThreadedAutoSuggest', 'DummyAutoSuggest', 'AutoSuggestFromHistory', 'ConditionalAutoSuggest', 'DynamicAutoSuggest', ] class Suggestion(object): """ Suggestion returned by an auto-suggest algorithm. :param text: The suggestion text. """ def __init__(self, text): self.text = text def __repr__(self): return 'Suggestion(%s)' % self.text class AutoSuggest(with_metaclass(ABCMeta, object)): """ Base class for auto suggestion implementations. """ @abstractmethod def get_suggestion(self, buffer, document): """ Return `None` or a :class:`.Suggestion` instance. We receive both :class:`~prompt_toolkit.buffer.Buffer` and :class:`~prompt_toolkit.document.Document`. The reason is that auto suggestions are retrieved asynchronously. (Like completions.) The buffer text could be changed in the meantime, but ``document`` contains the buffer document like it was at the start of the auto suggestion call. So, from here, don't access ``buffer.text``, but use ``document.text`` instead. :param buffer: The :class:`~prompt_toolkit.buffer.Buffer` instance. :param document: The :class:`~prompt_toolkit.document.Document` instance. """ def get_suggestion_future(self, buff, document): """ Return a :class:`.Future` which is set when the suggestions are ready. This function can be overloaded in order to provide an asynchronous implementation. """ return Future.succeed(self.get_suggestion(buff, document)) class ThreadedAutoSuggest(AutoSuggest): """ Wrapper that runs auto suggestions in a thread. (Use this to prevent the user interface from becoming unresponsive if the generation of suggestions takes too much time.) """ def __init__(self, auto_suggest): assert isinstance(auto_suggest, AutoSuggest) self.auto_suggest = auto_suggest def get_suggestion(self, buff, document): return self.auto_suggest.get_suggestion(buff, document) def get_suggestion_future(self, buff, document): """ Run the `get_suggestion` function in a thread. """ def run_get_suggestion_thread(): return self.get_suggestion(buff, document) f = run_in_executor(run_get_suggestion_thread) return f class DummyAutoSuggest(AutoSuggest): """ AutoSuggest class that doesn't return any suggestion. """ def get_suggestion(self, buffer, document): return # No suggestion class AutoSuggestFromHistory(AutoSuggest): """ Give suggestions based on the lines in the history. """ def get_suggestion(self, buffer, document): history = buffer.history # Consider only the last line for the suggestion. text = document.text.rsplit('\n', 1)[-1] # Only create a suggestion when this is not an empty line. if text.strip(): # Find first matching line in history. for string in reversed(list(history.get_strings())): for line in reversed(string.splitlines()): if line.startswith(text): return Suggestion(line[len(text):]) class ConditionalAutoSuggest(AutoSuggest): """ Auto suggest that can be turned on and of according to a certain condition. """ def __init__(self, auto_suggest, filter): assert isinstance(auto_suggest, AutoSuggest) self.auto_suggest = auto_suggest self.filter = to_filter(filter) def get_suggestion(self, buffer, document): if self.filter(): return self.auto_suggest.get_suggestion(buffer, document) class DynamicAutoSuggest(AutoSuggest): """ Validator class that can dynamically returns any Validator. :param get_validator: Callable that returns a :class:`.Validator` instance. """ def __init__(self, get_auto_suggest): assert callable(get_auto_suggest) self.get_auto_suggest = get_auto_suggest def get_suggestion(self, buff, document): auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() assert isinstance(auto_suggest, AutoSuggest) return auto_suggest.get_suggestion(buff, document) def get_suggestion_future(self, buff, document): auto_suggest = self.get_auto_suggest() or DummyAutoSuggest() assert isinstance(auto_suggest, AutoSuggest) return auto_suggest.get_suggestion_future(buff, document) prompt_toolkit-2.0.10/prompt_toolkit/buffer.py0000644000175100017510000017744613545407204023250 0ustar jonathanjonathan00000000000000""" Data structures for the Buffer. It holds the text, cursor position, history, etc... """ from __future__ import unicode_literals import os import re import shlex import subprocess import tempfile from functools import wraps import six from six.moves import range from .application.current import get_app from .application.run_in_terminal import run_in_terminal from .auto_suggest import AutoSuggest from .cache import FastDictCache from .clipboard import ClipboardData from .completion import ( CompleteEvent, Completer, Completion, DummyCompleter, get_common_complete_suffix, ) from .document import Document from .eventloop import From, Return, consume_async_generator, ensure_future from .filters import to_filter from .history import History, InMemoryHistory from .search import SearchDirection, SearchState from .selection import PasteMode, SelectionState, SelectionType from .utils import Event, test_callable_args, to_str from .validation import ValidationError, Validator __all__ = [ 'EditReadOnlyBuffer', 'Buffer', 'indent', 'unindent', 'reshape_text', ] class EditReadOnlyBuffer(Exception): " Attempt editing of read-only :class:`.Buffer`. " class ValidationState(object): " The validation state of a buffer. This is set after the validation. " VALID = 'VALID' INVALID = 'INVALID' UNKNOWN = 'UNKNOWN' class CompletionState(object): """ Immutable class that contains a completion state. """ def __init__(self, original_document, completions=None, complete_index=None): #: Document as it was when the completion started. self.original_document = original_document #: List of all the current Completion instances which are possible at #: this point. self.completions = completions or [] #: Position in the `completions` array. #: This can be `None` to indicate "no completion", the original text. self.complete_index = complete_index # Position in the `_completions` array. def __repr__(self): return '%s(%r, <%r> completions, index=%r)' % ( self.__class__.__name__, self.original_document, len(self.completions), self.complete_index) def go_to_index(self, index): """ Create a new :class:`.CompletionState` object with the new index. When `index` is `None` deselect the completion. """ if self.completions: assert index is None or 0 <= index < len(self.completions) self.complete_index = index def new_text_and_position(self): """ Return (new_text, new_cursor_position) for this completion. """ if self.complete_index is None: return self.original_document.text, self.original_document.cursor_position else: original_text_before_cursor = self.original_document.text_before_cursor original_text_after_cursor = self.original_document.text_after_cursor c = self.completions[self.complete_index] if c.start_position == 0: before = original_text_before_cursor else: before = original_text_before_cursor[:c.start_position] new_text = before + c.text + original_text_after_cursor new_cursor_position = len(before) + len(c.text) return new_text, new_cursor_position @property def current_completion(self): """ Return the current completion, or return `None` when no completion is selected. """ if self.complete_index is not None: return self.completions[self.complete_index] _QUOTED_WORDS_RE = re.compile(r"""(\s+|".*?"|'.*?')""") class YankNthArgState(object): """ For yank-last-arg/yank-nth-arg: Keep track of where we are in the history. """ def __init__(self, history_position=0, n=-1, previous_inserted_word=''): self.history_position = history_position self.previous_inserted_word = previous_inserted_word self.n = n def __repr__(self): return '%s(history_position=%r, n=%r, previous_inserted_word=%r)' % ( self.__class__.__name__, self.history_position, self.n, self.previous_inserted_word) class Buffer(object): """ The core data structure that holds the text and cursor position of the current input line and implements all text manipulations on top of it. It also implements the history, undo stack and the completion state. :param completer: :class:`~prompt_toolkit.completion.Completer` instance. :param history: :class:`~prompt_toolkit.history.History` instance. :param tempfile_suffix: The tempfile suffix (extension) to be used for the "open in editor" function. For a Python REPL, this would be ".py", so that the editor knows the syntax highlighting to use. This can also be a callable that returns a string. :param name: Name for this buffer. E.g. DEFAULT_BUFFER. This is mostly useful for key bindings where we sometimes prefer to refer to a buffer by their name instead of by reference. :param accept_handler: Called when the buffer input is accepted. (Usually when the user presses `enter`.) The accept handler receives this `Buffer` as input and should return True when the buffer text should be kept instead of calling reset. In case of a `PromptSession` for instance, we want to keep the text, because we will exit the application, and only reset it during the next run. Events: :param on_text_changed: When the buffer text changes. (Callable on None.) :param on_text_insert: When new text is inserted. (Callable on None.) :param on_cursor_position_changed: When the cursor moves. (Callable on None.) :param on_completions_changed: When the completions were changed. (Callable on None.) :param on_suggestion_set: When an auto-suggestion text has been set. (Callable on None.) Filters: :param complete_while_typing: :class:`~prompt_toolkit.filters.Filter` or `bool`. Decide whether or not to do asynchronous autocompleting while typing. :param validate_while_typing: :class:`~prompt_toolkit.filters.Filter` or `bool`. Decide whether or not to do asynchronous validation while typing. :param enable_history_search: :class:`~prompt_toolkit.filters.Filter` or `bool` to indicate when up-arrow partial string matching is enabled. It is advised to not enable this at the same time as `complete_while_typing`, because when there is an autocompletion found, the up arrows usually browse through the completions, rather than through the history. :param read_only: :class:`~prompt_toolkit.filters.Filter`. When True, changes will not be allowed. :param multiline: :class:`~prompt_toolkit.filters.Filter` or `bool`. When not set, pressing `Enter` will call the `accept_handler`. Otherwise, pressing `Esc-Enter` is required. """ def __init__(self, completer=None, auto_suggest=None, history=None, validator=None, tempfile_suffix='', name='', complete_while_typing=False, validate_while_typing=False, enable_history_search=False, document=None, accept_handler=None, read_only=False, multiline=True, on_text_changed=None, on_text_insert=None, on_cursor_position_changed=None, on_completions_changed=None, on_suggestion_set=None): # Accept both filters and booleans as input. enable_history_search = to_filter(enable_history_search) complete_while_typing = to_filter(complete_while_typing) validate_while_typing = to_filter(validate_while_typing) read_only = to_filter(read_only) multiline = to_filter(multiline) # Validate input. assert completer is None or isinstance(completer, Completer) assert auto_suggest is None or isinstance(auto_suggest, AutoSuggest) assert history is None or isinstance(history, History) assert validator is None or isinstance(validator, Validator) assert callable(tempfile_suffix) or isinstance(tempfile_suffix, six.text_type) assert isinstance(name, six.text_type) assert on_text_changed is None or callable(on_text_changed) assert on_text_insert is None or callable(on_text_insert) assert on_cursor_position_changed is None or callable(on_cursor_position_changed) assert on_completions_changed is None or callable(on_completions_changed) assert on_suggestion_set is None or callable(on_suggestion_set) assert document is None or isinstance(document, Document) assert accept_handler is None or (callable(accept_handler) and test_callable_args(accept_handler, [None])) self.completer = completer or DummyCompleter() self.auto_suggest = auto_suggest self.validator = validator self.tempfile_suffix = tempfile_suffix self.name = name self.accept_handler = accept_handler # Filters. (Usually, used by the key bindings to drive the buffer.) self.complete_while_typing = complete_while_typing self.validate_while_typing = validate_while_typing self.enable_history_search = enable_history_search self.read_only = read_only self.multiline = multiline # Text width. (For wrapping, used by the Vi 'gq' operator.) self.text_width = 0 #: The command buffer history. # Note that we shouldn't use a lazy 'or' here. bool(history) could be # False when empty. self.history = InMemoryHistory() if history is None else history self.__cursor_position = 0 # Events self.on_text_changed = Event(self, on_text_changed) self.on_text_insert = Event(self, on_text_insert) self.on_cursor_position_changed = Event(self, on_cursor_position_changed) self.on_completions_changed = Event(self, on_completions_changed) self.on_suggestion_set = Event(self, on_suggestion_set) # Document cache. (Avoid creating new Document instances.) self._document_cache = FastDictCache(Document, size=10) # Create completer / auto suggestion / validation coroutines. self._async_suggester = self._create_auto_suggest_coroutine() self._async_completer = self._create_completer_coroutine() self._async_validator = self._create_auto_validate_coroutine() # Reset other attributes. self.reset(document=document) # Attach callback for new history entries. def new_history_item(sender): # Insert the new string into `_working_lines`. self._working_lines.insert(0, self.history.get_strings()[0]) self.__working_index += 1 self.history.get_item_loaded_event().add_handler(new_history_item) self.history.start_loading() def __repr__(self): if len(self.text) < 15: text = self.text else: text = self.text[:12] + '...' return '' % (self.name, text, id(self)) def reset(self, document=None, append_to_history=False): """ :param append_to_history: Append current input to history first. """ assert document is None or isinstance(document, Document) if append_to_history: self.append_to_history() document = document or Document() self.__cursor_position = document.cursor_position # `ValidationError` instance. (Will be set when the input is wrong.) self.validation_error = None self.validation_state = ValidationState.UNKNOWN # State of the selection. self.selection_state = None # Multiple cursor mode. (When we press 'I' or 'A' in visual-block mode, # we can insert text on multiple lines at once. This is implemented by # using multiple cursors.) self.multiple_cursor_positions = [] # When doing consecutive up/down movements, prefer to stay at this column. self.preferred_column = None # State of complete browser self.complete_state = None # For interactive completion through Ctrl-N/Ctrl-P. # State of Emacs yank-nth-arg completion. self.yank_nth_arg_state = None # for yank-nth-arg. # Remember the document that we had *right before* the last paste # operation. This is used for rotating through the kill ring. self.document_before_paste = None # Current suggestion. self.suggestion = None # The history search text. (Used for filtering the history when we # browse through it.) self.history_search_text = None # Undo/redo stacks self._undo_stack = [] # Stack of (text, cursor_position) self._redo_stack = [] #: The working lines. Similar to history, except that this can be #: modified. The user can press arrow_up and edit previous entries. #: Ctrl-C should reset this, and copy the whole history back in here. #: Enter should process the current command and append to the real #: history. self._working_lines = self.history.get_strings()[:] self._working_lines.append(document.text) self.__working_index = len(self._working_lines) - 1 # def _set_text(self, value): """ set text at current working_index. Return whether it changed. """ working_index = self.working_index working_lines = self._working_lines original_value = working_lines[working_index] working_lines[working_index] = value # Return True when this text has been changed. if len(value) != len(original_value): # For Python 2, it seems that when two strings have a different # length and one is a prefix of the other, Python still scans # character by character to see whether the strings are different. # (Some benchmarking showed significant differences for big # documents. >100,000 of lines.) return True elif value != original_value: return True return False def _set_cursor_position(self, value): """ Set cursor position. Return whether it changed. """ original_position = self.__cursor_position self.__cursor_position = max(0, value) return value != original_position @property def text(self): return self._working_lines[self.working_index] @text.setter def text(self, value): """ Setting text. (When doing this, make sure that the cursor_position is valid for this text. text/cursor_position should be consistent at any time, otherwise set a Document instead.) """ assert isinstance(value, six.text_type), 'Got %r' % value # Ensure cursor position remains within the size of the text. if self.cursor_position > len(value): self.cursor_position = len(value) # Don't allow editing of read-only buffers. if self.read_only(): raise EditReadOnlyBuffer() changed = self._set_text(value) if changed: self._text_changed() # Reset history search text. # (Note that this doesn't need to happen when working_index # changes, which is when we traverse the history. That's why we # don't do this in `self._text_changed`.) self.history_search_text = None @property def cursor_position(self): return self.__cursor_position @cursor_position.setter def cursor_position(self, value): """ Setting cursor position. """ assert isinstance(value, int) # Ensure cursor position is within the size of the text. if value > len(self.text): value = len(self.text) if value < 0: value = 0 changed = self._set_cursor_position(value) if changed: self._cursor_position_changed() @property def working_index(self): return self.__working_index @working_index.setter def working_index(self, value): if self.__working_index != value: self.__working_index = value # Make sure to reset the cursor position, otherwise we end up in # situations where the cursor position is out of the bounds of the # text. self.cursor_position = 0 self._text_changed() def _text_changed(self): # Remove any validation errors and complete state. self.validation_error = None self.validation_state = ValidationState.UNKNOWN self.complete_state = None self.yank_nth_arg_state = None self.document_before_paste = None self.selection_state = None self.suggestion = None self.preferred_column = None # fire 'on_text_changed' event. self.on_text_changed.fire() # Input validation. # (This happens on all change events, unlike auto completion, also when # deleting text.) if self.validator and self.validate_while_typing(): ensure_future(self._async_validator()) def _cursor_position_changed(self): # Remove any complete state. # (Input validation should only be undone when the cursor position # changes.) self.complete_state = None self.yank_nth_arg_state = None self.document_before_paste = None # Unset preferred_column. (Will be set after the cursor movement, if # required.) self.preferred_column = None # Note that the cursor position can change if we have a selection the # new position of the cursor determines the end of the selection. # fire 'on_cursor_position_changed' event. self.on_cursor_position_changed.fire() @property def document(self): """ Return :class:`~prompt_toolkit.document.Document` instance from the current text, cursor position and selection state. """ return self._document_cache[ self.text, self.cursor_position, self.selection_state] @document.setter def document(self, value): """ Set :class:`~prompt_toolkit.document.Document` instance. This will set both the text and cursor position at the same time, but atomically. (Change events will be triggered only after both have been set.) """ self.set_document(value) def set_document(self, value, bypass_readonly=False): """ Set :class:`~prompt_toolkit.document.Document` instance. Like the ``document`` property, but accept an ``bypass_readonly`` argument. :param bypass_readonly: When True, don't raise an :class:`.EditReadOnlyBuffer` exception, even when the buffer is read-only. .. warning:: When this buffer is read-only and `bypass_readonly` was not passed, the `EditReadOnlyBuffer` exception will be caught by the `KeyProcessor` and is silently suppressed. This is important to keep in mind when writing key bindings, because it won't do what you expect, and there won't be a stack trace. Use try/finally around this function if you need some cleanup code. """ assert isinstance(value, Document) # Don't allow editing of read-only buffers. if not bypass_readonly and self.read_only(): raise EditReadOnlyBuffer() # Set text and cursor position first. text_changed = self._set_text(value.text) cursor_position_changed = self._set_cursor_position(value.cursor_position) # Now handle change events. (We do this when text/cursor position is # both set and consistent.) if text_changed: self._text_changed() self.history_search_text = None if cursor_position_changed: self._cursor_position_changed() @property def is_returnable(self): """ True when there is something handling accept. """ return bool(self.accept_handler) # End of def save_to_undo_stack(self, clear_redo_stack=True): """ Safe current state (input text and cursor position), so that we can restore it by calling undo. """ # Safe if the text is different from the text at the top of the stack # is different. If the text is the same, just update the cursor position. if self._undo_stack and self._undo_stack[-1][0] == self.text: self._undo_stack[-1] = (self._undo_stack[-1][0], self.cursor_position) else: self._undo_stack.append((self.text, self.cursor_position)) # Saving anything to the undo stack, clears the redo stack. if clear_redo_stack: self._redo_stack = [] def transform_lines(self, line_index_iterator, transform_callback): """ Transforms the text on a range of lines. When the iterator yield an index not in the range of lines that the document contains, it skips them silently. To uppercase some lines:: new_text = transform_lines(range(5,10), lambda text: text.upper()) :param line_index_iterator: Iterator of line numbers (int) :param transform_callback: callable that takes the original text of a line, and return the new text for this line. :returns: The new text. """ # Split lines lines = self.text.split('\n') # Apply transformation for index in line_index_iterator: try: lines[index] = transform_callback(lines[index]) except IndexError: pass return '\n'.join(lines) def transform_current_line(self, transform_callback): """ Apply the given transformation function to the current line. :param transform_callback: callable that takes a string and return a new string. """ document = self.document a = document.cursor_position + document.get_start_of_line_position() b = document.cursor_position + document.get_end_of_line_position() self.text = ( document.text[:a] + transform_callback(document.text[a:b]) + document.text[b:]) def transform_region(self, from_, to, transform_callback): """ Transform a part of the input string. :param from_: (int) start position. :param to: (int) end position. :param transform_callback: Callable which accepts a string and returns the transformed string. """ assert from_ < to self.text = ''.join([ self.text[:from_] + transform_callback(self.text[from_:to]) + self.text[to:] ]) def cursor_left(self, count=1): self.cursor_position += self.document.get_cursor_left_position(count=count) def cursor_right(self, count=1): self.cursor_position += self.document.get_cursor_right_position(count=count) def cursor_up(self, count=1): """ (for multiline edit). Move cursor to the previous line. """ original_column = self.preferred_column or self.document.cursor_position_col self.cursor_position += self.document.get_cursor_up_position( count=count, preferred_column=original_column) # Remember the original column for the next up/down movement. self.preferred_column = original_column def cursor_down(self, count=1): """ (for multiline edit). Move cursor to the next line. """ original_column = self.preferred_column or self.document.cursor_position_col self.cursor_position += self.document.get_cursor_down_position( count=count, preferred_column=original_column) # Remember the original column for the next up/down movement. self.preferred_column = original_column def auto_up(self, count=1, go_to_start_of_line_if_history_changes=False): """ If we're not on the first line (of a multiline input) go a line up, otherwise go back in history. (If nothing is selected.) """ if self.complete_state: self.complete_previous(count=count) elif self.document.cursor_position_row > 0: self.cursor_up(count=count) elif not self.selection_state: self.history_backward(count=count) # Go to the start of the line? if go_to_start_of_line_if_history_changes: self.cursor_position += self.document.get_start_of_line_position() def auto_down(self, count=1, go_to_start_of_line_if_history_changes=False): """ If we're not on the last line (of a multiline input) go a line down, otherwise go forward in history. (If nothing is selected.) """ if self.complete_state: self.complete_next(count=count) elif self.document.cursor_position_row < self.document.line_count - 1: self.cursor_down(count=count) elif not self.selection_state: self.history_forward(count=count) # Go to the start of the line? if go_to_start_of_line_if_history_changes: self.cursor_position += self.document.get_start_of_line_position() def delete_before_cursor(self, count=1): """ Delete specified number of characters before cursor and return the deleted text. """ assert count >= 0 deleted = '' if self.cursor_position > 0: deleted = self.text[self.cursor_position - count:self.cursor_position] new_text = self.text[:self.cursor_position - count] + self.text[self.cursor_position:] new_cursor_position = self.cursor_position - len(deleted) # Set new Document atomically. self.document = Document(new_text, new_cursor_position) return deleted def delete(self, count=1): """ Delete specified number of characters and Return the deleted text. """ if self.cursor_position < len(self.text): deleted = self.document.text_after_cursor[:count] self.text = self.text[:self.cursor_position] + \ self.text[self.cursor_position + len(deleted):] return deleted else: return '' def join_next_line(self, separator=' '): """ Join the next line to the current one by deleting the line ending after the current line. """ if not self.document.on_last_line: self.cursor_position += self.document.get_end_of_line_position() self.delete() # Remove spaces. self.text = (self.document.text_before_cursor + separator + self.document.text_after_cursor.lstrip(' ')) def join_selected_lines(self, separator=' '): """ Join the selected lines. """ assert self.selection_state # Get lines. from_, to = sorted([self.cursor_position, self.selection_state.original_cursor_position]) before = self.text[:from_] lines = self.text[from_:to].splitlines() after = self.text[to:] # Replace leading spaces with just one space. lines = [l.lstrip(' ') + separator for l in lines] # Set new document. self.document = Document(text=before + ''.join(lines) + after, cursor_position=len(before + ''.join(lines[:-1])) - 1) def swap_characters_before_cursor(self): """ Swap the last two characters before the cursor. """ pos = self.cursor_position if pos >= 2: a = self.text[pos - 2] b = self.text[pos - 1] self.text = self.text[:pos - 2] + b + a + self.text[pos:] def go_to_history(self, index): """ Go to this item in the history. """ if index < len(self._working_lines): self.working_index = index self.cursor_position = len(self.text) def complete_next(self, count=1, disable_wrap_around=False): """ Browse to the next completions. (Does nothing if there are no completion.) """ if self.complete_state: completions_count = len(self.complete_state.completions) if self.complete_state.complete_index is None: index = 0 elif self.complete_state.complete_index == completions_count - 1: index = None if disable_wrap_around: return else: index = min(completions_count - 1, self.complete_state.complete_index + count) self.go_to_completion(index) def complete_previous(self, count=1, disable_wrap_around=False): """ Browse to the previous completions. (Does nothing if there are no completion.) """ if self.complete_state: if self.complete_state.complete_index == 0: index = None if disable_wrap_around: return elif self.complete_state.complete_index is None: index = len(self.complete_state.completions) - 1 else: index = max(0, self.complete_state.complete_index - count) self.go_to_completion(index) def cancel_completion(self): """ Cancel completion, go back to the original text. """ if self.complete_state: self.go_to_completion(None) self.complete_state = None def _set_completions(self, completions): """ Start completions. (Generate list of completions and initialize.) By default, no completion will be selected. """ assert isinstance(completions, list) self.complete_state = CompletionState( original_document=self.document, completions=completions) # Trigger event. This should eventually invalidate the layout. self.on_completions_changed.fire() return self.complete_state def start_history_lines_completion(self): """ Start a completion based on all the other lines in the document and the history. """ found_completions = set() completions = [] # For every line of the whole history, find matches with the current line. current_line = self.document.current_line_before_cursor.lstrip() for i, string in enumerate(self._working_lines): for j, l in enumerate(string.split('\n')): l = l.strip() if l and l.startswith(current_line): # When a new line has been found. if l not in found_completions: found_completions.add(l) # Create completion. if i == self.working_index: display_meta = "Current, line %s" % (j + 1) else: display_meta = "History %s, line %s" % (i + 1, j + 1) completions.append(Completion( l, start_position=-len(current_line), display_meta=display_meta)) self._set_completions(completions=completions[::-1]) self.go_to_completion(0) def go_to_completion(self, index): """ Select a completion from the list of current completions. """ assert index is None or isinstance(index, int) assert self.complete_state # Set new completion state = self.complete_state state.go_to_index(index) # Set text/cursor position new_text, new_cursor_position = state.new_text_and_position() self.document = Document(new_text, new_cursor_position) # (changing text/cursor position will unset complete_state.) self.complete_state = state def apply_completion(self, completion): """ Insert a given completion. """ assert isinstance(completion, Completion) # If there was already a completion active, cancel that one. if self.complete_state: self.go_to_completion(None) self.complete_state = None # Insert text from the given completion. self.delete_before_cursor(-completion.start_position) self.insert_text(completion.text) def _set_history_search(self): """ Set `history_search_text`. (The text before the cursor will be used for filtering the history.) """ if self.enable_history_search(): if self.history_search_text is None: self.history_search_text = self.document.text_before_cursor else: self.history_search_text = None def _history_matches(self, i): """ True when the current entry matches the history search. (when we don't have history search, it's also True.) """ return (self.history_search_text is None or self._working_lines[i].startswith(self.history_search_text)) def history_forward(self, count=1): """ Move forwards through the history. :param count: Amount of items to move forward. """ self._set_history_search() # Go forward in history. found_something = False for i in range(self.working_index + 1, len(self._working_lines)): if self._history_matches(i): self.working_index = i count -= 1 found_something = True if count == 0: break # If we found an entry, move cursor to the end of the first line. if found_something: self.cursor_position = 0 self.cursor_position += self.document.get_end_of_line_position() def history_backward(self, count=1): """ Move backwards through history. """ self._set_history_search() # Go back in history. found_something = False for i in range(self.working_index - 1, -1, -1): if self._history_matches(i): self.working_index = i count -= 1 found_something = True if count == 0: break # If we move to another entry, move cursor to the end of the line. if found_something: self.cursor_position = len(self.text) def yank_nth_arg(self, n=None, _yank_last_arg=False): """ Pick nth word from previous history entry (depending on current `yank_nth_arg_state`) and insert it at current position. Rotate through history if called repeatedly. If no `n` has been given, take the first argument. (The second word.) :param n: (None or int), The index of the word from the previous line to take. """ assert n is None or isinstance(n, int) history_strings = self.history.get_strings() if not len(history_strings): return # Make sure we have a `YankNthArgState`. if self.yank_nth_arg_state is None: state = YankNthArgState(n=-1 if _yank_last_arg else 1) else: state = self.yank_nth_arg_state if n is not None: state.n = n # Get new history position. new_pos = state.history_position - 1 if -new_pos > len(history_strings): new_pos = -1 # Take argument from line. line = history_strings[new_pos] words = [w.strip() for w in _QUOTED_WORDS_RE.split(line)] words = [w for w in words if w] try: word = words[state.n] except IndexError: word = '' # Insert new argument. if state.previous_inserted_word: self.delete_before_cursor(len(state.previous_inserted_word)) self.insert_text(word) # Save state again for next completion. (Note that the 'insert' # operation from above clears `self.yank_nth_arg_state`.) state.previous_inserted_word = word state.history_position = new_pos self.yank_nth_arg_state = state def yank_last_arg(self, n=None): """ Like `yank_nth_arg`, but if no argument has been given, yank the last word by default. """ self.yank_nth_arg(n=n, _yank_last_arg=True) def start_selection(self, selection_type=SelectionType.CHARACTERS): """ Take the current cursor position as the start of this selection. """ self.selection_state = SelectionState(self.cursor_position, selection_type) def copy_selection(self, _cut=False): """ Copy selected text and return :class:`.ClipboardData` instance. Notice that this doesn't store the copied data on the clipboard yet. You can store it like this: .. code:: python data = buffer.copy_selection() get_app().clipboard.set_data(data) """ new_document, clipboard_data = self.document.cut_selection() if _cut: self.document = new_document self.selection_state = None return clipboard_data def cut_selection(self): """ Delete selected text and return :class:`.ClipboardData` instance. """ return self.copy_selection(_cut=True) def paste_clipboard_data(self, data, paste_mode=PasteMode.EMACS, count=1): """ Insert the data from the clipboard. """ assert isinstance(data, ClipboardData) assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) original_document = self.document self.document = self.document.paste_clipboard_data(data, paste_mode=paste_mode, count=count) # Remember original document. This assignment should come at the end, # because assigning to 'document' will erase it. self.document_before_paste = original_document def newline(self, copy_margin=True): """ Insert a line ending at the current position. """ if copy_margin: self.insert_text('\n' + self.document.leading_whitespace_in_current_line) else: self.insert_text('\n') def insert_line_above(self, copy_margin=True): """ Insert a new line above the current one. """ if copy_margin: insert = self.document.leading_whitespace_in_current_line + '\n' else: insert = '\n' self.cursor_position += self.document.get_start_of_line_position() self.insert_text(insert) self.cursor_position -= 1 def insert_line_below(self, copy_margin=True): """ Insert a new line below the current one. """ if copy_margin: insert = '\n' + self.document.leading_whitespace_in_current_line else: insert = '\n' self.cursor_position += self.document.get_end_of_line_position() self.insert_text(insert) def insert_text(self, data, overwrite=False, move_cursor=True, fire_event=True): """ Insert characters at cursor position. :param fire_event: Fire `on_text_insert` event. This is mainly used to trigger autocompletion while typing. """ # Original text & cursor position. otext = self.text ocpos = self.cursor_position # In insert/text mode. if overwrite: # Don't overwrite the newline itself. Just before the line ending, # it should act like insert mode. overwritten_text = otext[ocpos:ocpos + len(data)] if '\n' in overwritten_text: overwritten_text = overwritten_text[:overwritten_text.find('\n')] text = otext[:ocpos] + data + otext[ocpos + len(overwritten_text):] else: text = otext[:ocpos] + data + otext[ocpos:] if move_cursor: cpos = self.cursor_position + len(data) else: cpos = self.cursor_position # Set new document. # (Set text and cursor position at the same time. Otherwise, setting # the text will fire a change event before the cursor position has been # set. It works better to have this atomic.) self.document = Document(text, cpos) # Fire 'on_text_insert' event. if fire_event: # XXX: rename to `start_complete`. self.on_text_insert.fire() # Only complete when "complete_while_typing" is enabled. if self.completer and self.complete_while_typing(): ensure_future(self._async_completer()) # Call auto_suggest. if self.auto_suggest: ensure_future(self._async_suggester()) def undo(self): # Pop from the undo-stack until we find a text that if different from # the current text. (The current logic of `save_to_undo_stack` will # cause that the top of the undo stack is usually the same as the # current text, so in that case we have to pop twice.) while self._undo_stack: text, pos = self._undo_stack.pop() if text != self.text: # Push current text to redo stack. self._redo_stack.append((self.text, self.cursor_position)) # Set new text/cursor_position. self.document = Document(text, cursor_position=pos) break def redo(self): if self._redo_stack: # Copy current state on undo stack. self.save_to_undo_stack(clear_redo_stack=False) # Pop state from redo stack. text, pos = self._redo_stack.pop() self.document = Document(text, cursor_position=pos) def validate(self, set_cursor=False): """ Returns `True` if valid. :param set_cursor: Set the cursor position, if an error was found. """ # Don't call the validator again, if it was already called for the # current input. if self.validation_state != ValidationState.UNKNOWN: return self.validation_state == ValidationState.VALID # Call validator. if self.validator: try: self.validator.validate(self.document) except ValidationError as e: # Set cursor position (don't allow invalid values.) if set_cursor: self.cursor_position = min(max(0, e.cursor_position), len(self.text)) self.validation_state = ValidationState.INVALID self.validation_error = e return False # Handle validation result. self.validation_state = ValidationState.VALID self.validation_error = None return True def _validate_async(self): """ Asynchronous version of `validate()`. This one doesn't set the cursor position. We have both variants, because a synchronous version is required. Handling the ENTER key needs to be completely synchronous, otherwise stuff like type-ahead is going to give very weird results. (People could type input while the ENTER key is still processed.) An asynchronous version is required if we have `validate_while_typing` enabled. """ def coroutine(): # Don't call the validator again, if it was already called for the # current input. if self.validation_state != ValidationState.UNKNOWN: raise Return(self.validation_state == ValidationState.VALID) # Call validator. error = None document = self.document if self.validator: try: yield self.validator.get_validate_future(self.document) except ValidationError as e: error = e # If the document changed during the validation, try again. if self.document != document: result = yield From(coroutine()) raise Return(result) # Handle validation result. if error: self.validation_state = ValidationState.INVALID else: self.validation_state = ValidationState.VALID self.validation_error = error get_app().invalidate() # Trigger redraw (display error). raise Return(error is None) return ensure_future(coroutine()) def append_to_history(self): """ Append the current input to the history. """ # Save at the tail of the history. (But don't if the last entry the # history is already the same.) if self.text: history_strings = self.history.get_strings() if not len(history_strings) or history_strings[-1] != self.text: self.history.append_string(self.text) def _search(self, search_state, include_current_position=False, count=1): """ Execute search. Return (working_index, cursor_position) tuple when this search is applied. Returns `None` when this text cannot be found. """ assert isinstance(search_state, SearchState) assert isinstance(count, int) and count > 0 text = search_state.text direction = search_state.direction ignore_case = search_state.ignore_case() def search_once(working_index, document): """ Do search one time. Return (working_index, document) or `None` """ if direction == SearchDirection.FORWARD: # Try find at the current input. new_index = document.find( text, include_current_position=include_current_position, ignore_case=ignore_case) if new_index is not None: return (working_index, Document(document.text, document.cursor_position + new_index)) else: # No match, go forward in the history. (Include len+1 to wrap around.) # (Here we should always include all cursor positions, because # it's a different line.) for i in range(working_index + 1, len(self._working_lines) + 1): i %= len(self._working_lines) document = Document(self._working_lines[i], 0) new_index = document.find(text, include_current_position=True, ignore_case=ignore_case) if new_index is not None: return (i, Document(document.text, new_index)) else: # Try find at the current input. new_index = document.find_backwards( text, ignore_case=ignore_case) if new_index is not None: return (working_index, Document(document.text, document.cursor_position + new_index)) else: # No match, go back in the history. (Include -1 to wrap around.) for i in range(working_index - 1, -2, -1): i %= len(self._working_lines) document = Document(self._working_lines[i], len(self._working_lines[i])) new_index = document.find_backwards( text, ignore_case=ignore_case) if new_index is not None: return (i, Document(document.text, len(document.text) + new_index)) # Do 'count' search iterations. working_index = self.working_index document = self.document for _ in range(count): result = search_once(working_index, document) if result is None: return # Nothing found. else: working_index, document = result return (working_index, document.cursor_position) def document_for_search(self, search_state): """ Return a :class:`~prompt_toolkit.document.Document` instance that has the text/cursor position for this search, if we would apply it. This will be used in the :class:`~prompt_toolkit.layout.BufferControl` to display feedback while searching. """ search_result = self._search(search_state, include_current_position=True) if search_result is None: return self.document else: working_index, cursor_position = search_result # Keep selection, when `working_index` was not changed. if working_index == self.working_index: selection = self.selection_state else: selection = None return Document(self._working_lines[working_index], cursor_position, selection=selection) def get_search_position(self, search_state, include_current_position=True, count=1): """ Get the cursor position for this search. (This operation won't change the `working_index`. It's won't go through the history. Vi text objects can't span multiple items.) """ search_result = self._search( search_state, include_current_position=include_current_position, count=count) if search_result is None: return self.cursor_position else: working_index, cursor_position = search_result return cursor_position def apply_search(self, search_state, include_current_position=True, count=1): """ Apply search. If something is found, set `working_index` and `cursor_position`. """ search_result = self._search( search_state, include_current_position=include_current_position, count=count) if search_result is not None: working_index, cursor_position = search_result self.working_index = working_index self.cursor_position = cursor_position def exit_selection(self): self.selection_state = None def open_in_editor(self, validate_and_handle=False): """ Open code in editor. This returns a future, and runs in a thread executor. """ if self.read_only(): raise EditReadOnlyBuffer() # Write to temporary file descriptor, filename = tempfile.mkstemp(to_str(self.tempfile_suffix)) os.write(descriptor, self.text.encode('utf-8')) os.close(descriptor) def run(): try: # Open in editor # (We need to use `run_in_terminal`, because not all editors go to # the alternate screen buffer, and some could influence the cursor # position.) succes = yield From(run_in_terminal( lambda: self._open_file_in_editor(filename), in_executor=True)) # Read content again. if succes: with open(filename, 'rb') as f: text = f.read().decode('utf-8') # Drop trailing newline. (Editors are supposed to add it at the # end, but we don't need it.) if text.endswith('\n'): text = text[:-1] self.document = Document( text=text, cursor_position=len(text)) # Accept the input. if validate_and_handle: self.validate_and_handle() finally: # Clean up temp file. os.remove(filename) return ensure_future(run()) def _open_file_in_editor(self, filename): """ Call editor executable. Return True when we received a zero return code. """ # If the 'VISUAL' or 'EDITOR' environment variable has been set, use that. # Otherwise, fall back to the first available editor that we can find. visual = os.environ.get('VISUAL') editor = os.environ.get('EDITOR') editors = [ visual, editor, # Order of preference. '/usr/bin/editor', '/usr/bin/nano', '/usr/bin/pico', '/usr/bin/vi', '/usr/bin/emacs', ] for e in editors: if e: try: # Use 'shlex.split()', because $VISUAL can contain spaces # and quotes. returncode = subprocess.call(shlex.split(e) + [filename]) return returncode == 0 except OSError: # Executable does not exist, try the next one. pass return False def start_completion(self, select_first=False, select_last=False, insert_common_part=False, complete_event=None): """ Start asynchronous autocompletion of this buffer. (This will do nothing if a previous completion was still in progress.) """ # Only one of these options can be selected. assert select_first + select_last + insert_common_part <= 1 ensure_future(self._async_completer( select_first=select_first, select_last=select_last, insert_common_part=insert_common_part, complete_event=complete_event or CompleteEvent(completion_requested=True))) def _create_completer_coroutine(self): """ Create function for asynchronous autocompletion. (This consumes the asynchronous completer generator, which possibly runs the completion algorithm in another thread.) """ def completion_does_nothing(document, completion): """ Return `True` if applying this completion doesn't have any effect. (When it doesn't insert any new text. """ text_before_cursor = document.text_before_cursor replaced_text = text_before_cursor[ len(text_before_cursor) + completion.start_position:] return replaced_text == completion.text @_only_one_at_a_time def async_completer(select_first=False, select_last=False, insert_common_part=False, complete_event=None): document = self.document complete_event = complete_event or CompleteEvent(text_inserted=True) # Don't complete when we already have completions. if self.complete_state or not self.completer: return # Create an empty CompletionState. complete_state = CompletionState(original_document=self.document) self.complete_state = complete_state def proceed(): """ Keep retrieving completions. Input text has not yet changed while generating completions. """ return self.complete_state == complete_state def add_completion(completion): " Got one completion from the asynchronous completion generator. " complete_state.completions.append(completion) self.on_completions_changed.fire() yield From(consume_async_generator( self.completer.get_completions_async(document, complete_event), item_callback=add_completion, cancel=lambda: not proceed())) completions = complete_state.completions # When there is only one completion, which has nothing to add, ignore it. if (len(completions) == 1 and completion_does_nothing(document, completions[0])): del completions[:] # Set completions if the text was not yet changed. if proceed(): # When no completions were found, or when the user selected # already a completion by using the arrow keys, don't do anything. if not self.complete_state or self.complete_state.complete_index is not None: return # When there are no completions, reset completion state anyway. if not completions: self.complete_state = None # Render the ui if the completion menu was shown # it is needed especially if there is one completion and it was deleted. self.on_completions_changed.fire() return # Select first/last or insert common part, depending on the key # binding. (For this we have to wait until all completions are # loaded.) if select_first: self.go_to_completion(0) elif select_last: self.go_to_completion(len(completions) - 1) elif insert_common_part: common_part = get_common_complete_suffix(document, completions) if common_part: # Insert the common part, update completions. self.insert_text(common_part) if len(completions) > 1: # (Don't call `async_completer` again, but # recalculate completions. See: # https://github.com/ipython/ipython/issues/9658) completions[:] = [ c.new_completion_from_position(len(common_part)) for c in completions] self._set_completions(completions=completions) else: self.complete_state = None else: # When we were asked to insert the "common" # prefix, but there was no common suffix but # still exactly one match, then select the # first. (It could be that we have a completion # which does * expansion, like '*.py', with # exactly one match.) if len(completions) == 1: self.go_to_completion(0) else: # If the last operation was an insert, (not a delete), restart # the completion coroutine. if self.document.text_before_cursor == document.text_before_cursor: return # Nothing changed. if self.document.text_before_cursor.startswith(document.text_before_cursor): raise _Retry return async_completer def _create_auto_suggest_coroutine(self): """ Create function for asynchronous auto suggestion. (This can be in another thread.) """ @_only_one_at_a_time def async_suggestor(): document = self.document # Don't suggest when we already have a suggestion. if self.suggestion or not self.auto_suggest: return suggestion = yield From(self.auto_suggest.get_suggestion_future( self, document)) # Set suggestion only if the text was not yet changed. if self.document == document: # Set suggestion and redraw interface. self.suggestion = suggestion self.on_suggestion_set.fire() else: # Otherwise, restart thread. raise _Retry return async_suggestor def _create_auto_validate_coroutine(self): """ Create a function for asynchronous validation while typing. (This can be in another thread.) """ @_only_one_at_a_time def async_validator(): yield From(self._validate_async()) return async_validator def validate_and_handle(self): """ Validate buffer and handle the accept action. """ valid = self.validate(set_cursor=True) # When the validation succeeded, accept the input. if valid: if self.accept_handler: keep_text = self.accept_handler(self) else: keep_text = False self.append_to_history() if not keep_text: self.reset() def _only_one_at_a_time(coroutine): """ Decorator that only starts the coroutine only if the previous call has finished. (Used to make sure that we have only one autocompleter, auto suggestor and validator running at a time.) When the coroutine raises `_Retry`, it is restarted. """ running = [False] @wraps(coroutine) def new_coroutine(*a, **kw): # Don't start a new function, if the previous is still in progress. if running[0]: return running[0] = True try: while True: try: yield From(coroutine(*a, **kw)) except _Retry: continue else: raise Return(None) finally: running[0] = False return new_coroutine class _Retry(Exception): " Retry in `_only_one_at_a_time`. " def indent(buffer, from_row, to_row, count=1): """ Indent text of a :class:`.Buffer` object. """ current_row = buffer.document.cursor_position_row line_range = range(from_row, to_row) # Apply transformation. new_text = buffer.transform_lines(line_range, lambda l: ' ' * count + l) buffer.document = Document( new_text, Document(new_text).translate_row_col_to_index(current_row, 0)) # Go to the start of the line. buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) def unindent(buffer, from_row, to_row, count=1): """ Unindent text of a :class:`.Buffer` object. """ current_row = buffer.document.cursor_position_row line_range = range(from_row, to_row) def transform(text): remove = ' ' * count if text.startswith(remove): return text[len(remove):] else: return text.lstrip() # Apply transformation. new_text = buffer.transform_lines(line_range, transform) buffer.document = Document( new_text, Document(new_text).translate_row_col_to_index(current_row, 0)) # Go to the start of the line. buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) def reshape_text(buffer, from_row, to_row): """ Reformat text, taking the width into account. `to_row` is included. (Vi 'gq' operator.) """ lines = buffer.text.splitlines(True) lines_before = lines[:from_row] lines_after = lines[to_row + 1:] lines_to_reformat = lines[from_row:to_row + 1] if lines_to_reformat: # Take indentation from the first line. length = re.search(r'^\s*', lines_to_reformat[0]).end() indent = lines_to_reformat[0][:length].replace('\n', '') # Now, take all the 'words' from the lines to be reshaped. words = ''.join(lines_to_reformat).split() # And reshape. width = (buffer.text_width or 80) - len(indent) reshaped_text = [indent] current_width = 0 for w in words: if current_width: if len(w) + current_width + 1 > width: reshaped_text.append('\n') reshaped_text.append(indent) current_width = 0 else: reshaped_text.append(' ') current_width += 1 reshaped_text.append(w) current_width += len(w) if reshaped_text[-1] != '\n': reshaped_text.append('\n') # Apply result. buffer.document = Document( text=''.join(lines_before + reshaped_text + lines_after), cursor_position=len(''.join(lines_before + reshaped_text))) prompt_toolkit-2.0.10/prompt_toolkit/cache.py0000644000175100017510000000652513545407204023027 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from collections import deque from functools import wraps __all__ = [ 'SimpleCache', 'FastDictCache', 'memoized', ] class SimpleCache(object): """ Very simple cache that discards the oldest item when the cache size is exceeded. :param maxsize: Maximum size of the cache. (Don't make it too big.) """ def __init__(self, maxsize=8): assert isinstance(maxsize, int) and maxsize > 0 self._data = {} self._keys = deque() self.maxsize = maxsize def get(self, key, getter_func): """ Get object from the cache. If not found, call `getter_func` to resolve it, and put that on the top of the cache instead. """ # Look in cache first. try: return self._data[key] except KeyError: # Not found? Get it. value = getter_func() self._data[key] = value self._keys.append(key) # Remove the oldest key when the size is exceeded. if len(self._data) > self.maxsize: key_to_remove = self._keys.popleft() if key_to_remove in self._data: del self._data[key_to_remove] return value def clear(self): " Clear cache. " self._data = {} self._keys = deque() class FastDictCache(dict): """ Fast, lightweight cache which keeps at most `size` items. It will discard the oldest items in the cache first. The cache is a dictionary, which doesn't keep track of access counts. It is perfect to cache little immutable objects which are not expensive to create, but where a dictionary lookup is still much faster than an object instantiation. :param get_value: Callable that's called in case of a missing key. """ # NOTE: This cache is used to cache `prompt_toolkit.layout.screen.Char` and # `prompt_toolkit.Document`. Make sure to keep this really lightweight. # Accessing the cache should stay faster than instantiating new # objects. # (Dictionary lookups are really fast.) # SimpleCache is still required for cases where the cache key is not # the same as the arguments given to the function that creates the # value.) def __init__(self, get_value=None, size=1000000): assert callable(get_value) assert isinstance(size, int) and size > 0 self._keys = deque() self.get_value = get_value self.size = size def __missing__(self, key): # Remove the oldest key when the size is exceeded. if len(self) > self.size: key_to_remove = self._keys.popleft() if key_to_remove in self: del self[key_to_remove] result = self.get_value(*key) self[key] = result self._keys.append(key) return result def memoized(maxsize=1024): """ Memoization decorator for immutable classes and pure functions. """ def decorator(obj): cache = SimpleCache(maxsize=maxsize) @wraps(obj) def new_callable(*a, **kw): def create_new(): return obj(*a, **kw) key = (a, tuple(sorted(kw.items()))) return cache.get(key, create_new) return new_callable return decorator prompt_toolkit-2.0.10/prompt_toolkit/clipboard/0000755000175100017510000000000013545410361023337 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/clipboard/__init__.py0000644000175100017510000000067313545407204025460 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import Clipboard, ClipboardData, DummyClipboard, DynamicClipboard from .in_memory import InMemoryClipboard # We are not importing `PyperclipClipboard` here, because it would require the # `pyperclip` module to be present. #from .pyperclip import PyperclipClipboard __all__ = [ 'Clipboard', 'ClipboardData', 'DummyClipboard', 'DynamicClipboard', 'InMemoryClipboard', ] prompt_toolkit-2.0.10/prompt_toolkit/clipboard/base.py0000644000175100017510000000502413545407204024626 0ustar jonathanjonathan00000000000000""" Clipboard for command line interface. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod import six from six import with_metaclass from prompt_toolkit.selection import SelectionType __all__ = [ 'Clipboard', 'ClipboardData', 'DummyClipboard', 'DynamicClipboard', ] class ClipboardData(object): """ Text on the clipboard. :param text: string :param type: :class:`~prompt_toolkit.selection.SelectionType` """ def __init__(self, text='', type=SelectionType.CHARACTERS): assert isinstance(text, six.string_types) assert type in (SelectionType.CHARACTERS, SelectionType.LINES, SelectionType.BLOCK) self.text = text self.type = type class Clipboard(with_metaclass(ABCMeta, object)): """ Abstract baseclass for clipboards. (An implementation can be in memory, it can share the X11 or Windows keyboard, or can be persistent.) """ @abstractmethod def set_data(self, data): """ Set data to the clipboard. :param data: :class:`~.ClipboardData` instance. """ def set_text(self, text): # Not abstract. """ Shortcut for setting plain text on clipboard. """ assert isinstance(text, six.string_types) self.set_data(ClipboardData(text)) def rotate(self): """ For Emacs mode, rotate the kill ring. """ @abstractmethod def get_data(self): """ Return clipboard data. """ class DummyClipboard(Clipboard): """ Clipboard implementation that doesn't remember anything. """ def set_data(self, data): pass def set_text(self, text): pass def rotate(self): pass def get_data(self): return ClipboardData() class DynamicClipboard(Clipboard): """ Clipboard class that can dynamically returns any Clipboard. :param get_clipboard: Callable that returns a :class:`.Clipboard` instance. """ def __init__(self, get_clipboard): assert callable(get_clipboard) self.get_clipboard = get_clipboard def _clipboard(self): clipboard = self.get_clipboard() or DummyClipboard() assert isinstance(clipboard, Clipboard) return clipboard def set_data(self, data): self._clipboard().set_data(data) def set_text(self, text): self._clipboard().set_text(text) def rotate(self): self._clipboard().rotate() def get_data(self): return self._clipboard().get_data() prompt_toolkit-2.0.10/prompt_toolkit/clipboard/in_memory.py0000644000175100017510000000205013545407204025706 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from collections import deque from .base import Clipboard, ClipboardData __all__ = [ 'InMemoryClipboard', ] class InMemoryClipboard(Clipboard): """ Default clipboard implementation. Just keep the data in memory. This implements a kill-ring, for Emacs mode. """ def __init__(self, data=None, max_size=60): assert data is None or isinstance(data, ClipboardData) assert max_size >= 1 self.max_size = max_size self._ring = deque() if data is not None: self.set_data(data) def set_data(self, data): assert isinstance(data, ClipboardData) self._ring.appendleft(data) while len(self._ring) > self.max_size: self._ring.pop() def get_data(self): if self._ring: return self._ring[0] else: return ClipboardData() def rotate(self): if self._ring: # Add the very first item at the end. self._ring.append(self._ring.popleft()) prompt_toolkit-2.0.10/prompt_toolkit/clipboard/pyperclip.py0000644000175100017510000000216213545407204025723 0ustar jonathanjonathan00000000000000from __future__ import absolute_import, unicode_literals import pyperclip from prompt_toolkit.selection import SelectionType from .base import Clipboard, ClipboardData __all__ = [ 'PyperclipClipboard', ] class PyperclipClipboard(Clipboard): """ Clipboard that synchronizes with the Windows/Mac/Linux system clipboard, using the pyperclip module. """ def __init__(self): self._data = None def set_data(self, data): assert isinstance(data, ClipboardData) self._data = data pyperclip.copy(data.text) def get_data(self): text = pyperclip.paste() # When the clipboard data is equal to what we copied last time, reuse # the `ClipboardData` instance. That way we're sure to keep the same # `SelectionType`. if self._data and self._data.text == text: return self._data # Pyperclip returned something else. Create a new `ClipboardData` # instance. else: return ClipboardData( text=text, type=SelectionType.LINES if '\n' in text else SelectionType.LINES) prompt_toolkit-2.0.10/prompt_toolkit/completion/0000755000175100017510000000000013545410361023551 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/completion/__init__.py0000644000175100017510000000140013545407204025657 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import ( CompleteEvent, Completer, Completion, DummyCompleter, DynamicCompleter, ThreadedCompleter, get_common_complete_suffix, merge_completers, ) from .filesystem import ExecutableCompleter, PathCompleter from .fuzzy_completer import FuzzyCompleter, FuzzyWordCompleter from .word_completer import WordCompleter __all__ = [ # Base. 'Completion', 'Completer', 'ThreadedCompleter', 'DummyCompleter', 'DynamicCompleter', 'CompleteEvent', 'merge_completers', 'get_common_complete_suffix', # Filesystem. 'PathCompleter', 'ExecutableCompleter', # Word completer. 'WordCompleter', 'FuzzyCompleter', 'FuzzyWordCompleter', ] prompt_toolkit-2.0.10/prompt_toolkit/completion/base.py0000644000175100017510000002503213545407204025041 0ustar jonathanjonathan00000000000000""" """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import text_type, with_metaclass from prompt_toolkit.eventloop import ( AsyncGeneratorItem, generator_to_async_generator, ) __all__ = [ 'Completion', 'Completer', 'ThreadedCompleter', 'DummyCompleter', 'DynamicCompleter', 'CompleteEvent', 'merge_completers', 'get_common_complete_suffix', ] class Completion(object): """ :param text: The new string that will be inserted into the document. :param start_position: Position relative to the cursor_position where the new text will start. The text will be inserted between the start_position and the original cursor position. :param display: (optional string or formatted text) If the completion has to be displayed differently in the completion menu. :param display_meta: (Optional string or formatted text) Meta information about the completion, e.g. the path or source where it's coming from. This can also be a callable that returns a string. :param style: Style string. :param selected_style: Style string, used for a selected completion. This can override the `style` parameter. """ def __init__(self, text, start_position=0, display=None, display_meta=None, style='', selected_style=''): assert isinstance(text, text_type) assert isinstance(start_position, int) assert isinstance(style, text_type) assert isinstance(selected_style, text_type) from prompt_toolkit.formatted_text import to_formatted_text self.text = text self.start_position = start_position self._display_meta = display_meta if display is None: display = text self.display = to_formatted_text(display) self.style = style self.selected_style = selected_style assert self.start_position <= 0 def __repr__(self): if self.display == self.text: return '%s(text=%r, start_position=%r)' % ( self.__class__.__name__, self.text, self.start_position) else: return '%s(text=%r, start_position=%r, display=%r)' % ( self.__class__.__name__, self.text, self.start_position, self.display) def __eq__(self, other): return ( self.text == other.text and self.start_position == other.start_position and self.display == other.display and self._display_meta == other._display_meta) def __hash__(self): return hash((self.text, self.start_position, self.display, self._display_meta)) @property def display_text(self): " The 'display' field as plain text. " from prompt_toolkit.formatted_text import fragment_list_to_text return fragment_list_to_text(self.display) @property def display_meta(self): " Return meta-text. (This is lazy when using a callable). " from prompt_toolkit.formatted_text import to_formatted_text return to_formatted_text(self._display_meta) @property def display_meta_text(self): " The 'meta' field as plain text. " from prompt_toolkit.formatted_text import fragment_list_to_text return fragment_list_to_text(self.display_meta) def new_completion_from_position(self, position): """ (Only for internal use!) Get a new completion by splitting this one. Used by `Application` when it needs to have a list of new completions after inserting the common prefix. """ assert isinstance(position, int) and position - self.start_position >= 0 return Completion( text=self.text[position - self.start_position:], display=self.display, display_meta=self._display_meta) class CompleteEvent(object): """ Event that called the completer. :param text_inserted: When True, it means that completions are requested because of a text insert. (`Buffer.complete_while_typing`.) :param completion_requested: When True, it means that the user explicitly pressed the `Tab` key in order to view the completions. These two flags can be used for instance to implement a completer that shows some completions when ``Tab`` has been pressed, but not automatically when the user presses a space. (Because of `complete_while_typing`.) """ def __init__(self, text_inserted=False, completion_requested=False): assert not (text_inserted and completion_requested) #: Automatic completion while typing. self.text_inserted = text_inserted #: Used explicitly requested completion by pressing 'tab'. self.completion_requested = completion_requested def __repr__(self): return '%s(text_inserted=%r, completion_requested=%r)' % ( self.__class__.__name__, self.text_inserted, self.completion_requested) class Completer(with_metaclass(ABCMeta, object)): """ Base class for completer implementations. """ @abstractmethod def get_completions(self, document, complete_event): """ This should be a generator that yields :class:`.Completion` instances. If the generation of completions is something expensive (that takes a lot of time), consider wrapping this `Completer` class in a `ThreadedCompleter`. In that case, the completer algorithm runs in a background thread and completions will be displayed as soon as they arrive. :param document: :class:`~prompt_toolkit.document.Document` instance. :param complete_event: :class:`.CompleteEvent` instance. """ while False: yield def get_completions_async(self, document, complete_event): """ Asynchronous generator for completions. (Probably, you won't have to override this.) This should return an iterable that can yield both :class:`.Completion` and `Future` objects. The :class:`.Completion` objects have to be wrapped in a `AsyncGeneratorItem` object. If we drop Python 2 support in the future, this could become a true asynchronous generator. """ for item in self.get_completions(document, complete_event): assert isinstance(item, Completion) yield AsyncGeneratorItem(item) class ThreadedCompleter(Completer): """ Wrapper that runs the `get_completions` generator in a thread. (Use this to prevent the user interface from becoming unresponsive if the generation of completions takes too much time.) The completions will be displayed as soon as they are produced. The user can already select a completion, even if not all completions are displayed. """ def __init__(self, completer=None): assert isinstance(completer, Completer), 'Got %r' % (completer, ) self.completer = completer def get_completions(self, document, complete_event): return self.completer.get_completions(document, complete_event) def get_completions_async(self, document, complete_event): """ Asynchronous generator of completions. This yields both Future and Completion objects. """ return generator_to_async_generator( lambda: self.completer.get_completions(document, complete_event)) def __repr__(self): return 'ThreadedCompleter(%r)' % (self.completer, ) class DummyCompleter(Completer): """ A completer that doesn't return any completion. """ def get_completions(self, document, complete_event): return [] def __repr__(self): return 'DummyCompleter()' class DynamicCompleter(Completer): """ Completer class that can dynamically returns any Completer. :param get_completer: Callable that returns a :class:`.Completer` instance. """ def __init__(self, get_completer): assert callable(get_completer) self.get_completer = get_completer def get_completions(self, document, complete_event): completer = self.get_completer() or DummyCompleter() return completer.get_completions(document, complete_event) def get_completions_async(self, document, complete_event): completer = self.get_completer() or DummyCompleter() return completer.get_completions_async(document, complete_event) def __repr__(self): return 'DynamicCompleter(%r -> %r)' % ( self.get_completer, self.get_completer()) class _MergedCompleter(Completer): """ Combine several completers into one. """ def __init__(self, completers): assert all(isinstance(c, Completer) for c in completers) self.completers = completers def get_completions(self, document, complete_event): # Get all completions from the other completers in a blocking way. for completer in self.completers: for c in completer.get_completions(document, complete_event): yield c def get_completions_async(self, document, complete_event): # Get all completions from the other completers in a blocking way. for completer in self.completers: # Consume async generator -> item can be `AsyncGeneratorItem` or # `Future`. for item in completer.get_completions_async(document, complete_event): yield item def merge_completers(completers): """ Combine several completers into one. """ return _MergedCompleter(completers) def get_common_complete_suffix(document, completions): """ Return the common prefix for all completions. """ # Take only completions that don't change the text before the cursor. def doesnt_change_before_cursor(completion): end = completion.text[:-completion.start_position] return document.text_before_cursor.endswith(end) completions2 = [c for c in completions if doesnt_change_before_cursor(c)] # When there is at least one completion that changes the text before the # cursor, don't return any common part. if len(completions2) != len(completions): return '' # Return the common prefix. def get_suffix(completion): return completion.text[-completion.start_position:] return _commonprefix([get_suffix(c) for c in completions2]) def _commonprefix(strings): # Similar to os.path.commonprefix if not strings: return '' else: s1 = min(strings) s2 = max(strings) for i, c in enumerate(s1): if c != s2[i]: return s1[:i] return s1 prompt_toolkit-2.0.10/prompt_toolkit/completion/filesystem.py0000644000175100017510000000726313545407204026321 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import os from prompt_toolkit.completion import Completer, Completion __all__ = [ 'PathCompleter', 'ExecutableCompleter', ] class PathCompleter(Completer): """ Complete for Path variables. :param get_paths: Callable which returns a list of directories to look into when the user enters a relative path. :param file_filter: Callable which takes a filename and returns whether this file should show up in the completion. ``None`` when no filtering has to be done. :param min_input_len: Don't do autocompletion when the input string is shorter. """ def __init__(self, only_directories=False, get_paths=None, file_filter=None, min_input_len=0, expanduser=False): assert get_paths is None or callable(get_paths) assert file_filter is None or callable(file_filter) assert isinstance(min_input_len, int) assert isinstance(expanduser, bool) self.only_directories = only_directories self.get_paths = get_paths or (lambda: ['.']) self.file_filter = file_filter or (lambda _: True) self.min_input_len = min_input_len self.expanduser = expanduser def get_completions(self, document, complete_event): text = document.text_before_cursor # Complete only when we have at least the minimal input length, # otherwise, we can too many results and autocompletion will become too # heavy. if len(text) < self.min_input_len: return try: # Do tilde expansion. if self.expanduser: text = os.path.expanduser(text) # Directories where to look. dirname = os.path.dirname(text) if dirname: directories = [os.path.dirname(os.path.join(p, text)) for p in self.get_paths()] else: directories = self.get_paths() # Start of current file. prefix = os.path.basename(text) # Get all filenames. filenames = [] for directory in directories: # Look for matches in this directory. if os.path.isdir(directory): for filename in os.listdir(directory): if filename.startswith(prefix): filenames.append((directory, filename)) # Sort filenames = sorted(filenames, key=lambda k: k[1]) # Yield them. for directory, filename in filenames: completion = filename[len(prefix):] full_name = os.path.join(directory, filename) if os.path.isdir(full_name): # For directories, add a slash to the filename. # (We don't add them to the `completion`. Users can type it # to trigger the autocompletion themselves.) filename += '/' elif self.only_directories: continue if not self.file_filter(full_name): continue yield Completion(completion, 0, display=filename) except OSError: pass class ExecutableCompleter(PathCompleter): """ Complete only executable files in the current path. """ def __init__(self): PathCompleter.__init__( self, only_directories=False, min_input_len=1, get_paths=lambda: os.environ.get('PATH', '').split(os.pathsep), file_filter=lambda name: os.access(name, os.X_OK), expanduser=True), prompt_toolkit-2.0.10/prompt_toolkit/completion/fuzzy_completer.py0000644000175100017510000001424213545407204027371 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import re from collections import namedtuple from six import string_types from prompt_toolkit.document import Document from prompt_toolkit.filters import to_filter from .base import Completer, Completion from .word_completer import WordCompleter __all__ = [ 'FuzzyCompleter', 'FuzzyWordCompleter', ] class FuzzyCompleter(Completer): """ Fuzzy completion. This wraps any other completer and turns it into a fuzzy completer. If the list of words is: ["leopard" , "gorilla", "dinosaur", "cat", "bee"] Then trying to complete "oar" would yield "leopard" and "dinosaur", but not the others, because they match the regular expression 'o.*a.*r'. Similar, in another application "djm" could expand to "django_migrations". The results are sorted by relevance, which is defined as the start position and the length of the match. Notice that this is not really a tool to work around spelling mistakes, like what would be possible with difflib. The purpose is rather to have a quicker or more intuitive way to filter the given completions, especially when many completions have a common prefix. Fuzzy algorithm is based on this post: https://blog.amjith.com/fuzzyfinder-in-10-lines-of-python :param completer: A :class:`~.Completer` instance. :param WORD: When True, use WORD characters. :param pattern: Regex pattern which selects the characters before the cursor that are considered for the fuzzy matching. :param enable_fuzzy: (bool or `Filter`) Enabled the fuzzy behavior. For easily turning fuzzyness on or off according to a certain condition. """ def __init__(self, completer, WORD=False, pattern=None, enable_fuzzy=True): assert isinstance(completer, Completer) assert pattern is None or pattern.startswith('^') self.completer = completer self.pattern = pattern self.WORD = WORD self.pattern = pattern self.enable_fuzzy = to_filter(enable_fuzzy) def get_completions(self, document, complete_event): if self.enable_fuzzy(): return self._get_fuzzy_completions(document, complete_event) else: return self.completer.get_completions(document, complete_event) def _get_pattern(self): if self.pattern: return self.pattern if self.WORD: return r'[^\s]+' return '^[a-zA-Z0-9_]*' def _get_fuzzy_completions(self, document, complete_event): word_before_cursor = document.get_word_before_cursor( pattern=re.compile(self._get_pattern())) # Get completions document2 = Document( text=document.text[:document.cursor_position - len(word_before_cursor)], cursor_position=document.cursor_position - len(word_before_cursor)) completions = list(self.completer.get_completions(document2, complete_event)) fuzzy_matches = [] pat = '.*?'.join(map(re.escape, word_before_cursor)) pat = '(?=({0}))'.format(pat) # lookahead regex to manage overlapping matches regex = re.compile(pat, re.IGNORECASE) for compl in completions: matches = list(regex.finditer(compl.text)) if matches: # Prefer the match, closest to the left, then shortest. best = min(matches, key=lambda m: (m.start(), len(m.group(1)))) fuzzy_matches.append(_FuzzyMatch(len(best.group(1)), best.start(), compl)) def sort_key(fuzzy_match): " Sort by start position, then by the length of the match. " return fuzzy_match.start_pos, fuzzy_match.match_length fuzzy_matches = sorted(fuzzy_matches, key=sort_key) for match in fuzzy_matches: # Include these completions, but set the correct `display` # attribute and `start_position`. yield Completion( match.completion.text, start_position=match.completion.start_position - len(word_before_cursor), display_meta=match.completion.display_meta, display=self._get_display(match, word_before_cursor), style=match.completion.style) def _get_display(self, fuzzy_match, word_before_cursor): """ Generate formatted text for the display label. """ m = fuzzy_match word = m.completion.text if m.match_length == 0: # No highlighting when we have zero length matches (no input text). return word result = [] # Text before match. result.append(('class:fuzzymatch.outside', word[:m.start_pos])) # The match itself. characters = list(word_before_cursor) for c in word[m.start_pos:m.start_pos + m.match_length]: classname = 'class:fuzzymatch.inside' if characters and c.lower() == characters[0].lower(): classname += '.character' del characters[0] result.append((classname, c)) # Text after match. result.append( ('class:fuzzymatch.outside', word[m.start_pos + m.match_length:])) return result class FuzzyWordCompleter(Completer): """ Fuzzy completion on a list of words. (This is basically a `WordCompleter` wrapped in a `FuzzyCompleter`.) :param words: List of words or callable that returns a list of words. :param meta_dict: Optional dict mapping words to their meta-information. :param WORD: When True, use WORD characters. """ def __init__(self, words, meta_dict=None, WORD=False): assert callable(words) or all(isinstance(w, string_types) for w in words) self.words = words self.meta_dict = meta_dict or {} self.WORD = WORD self.word_completer = WordCompleter( words=lambda: self.words, WORD=self.WORD) self.fuzzy_completer = FuzzyCompleter( self.word_completer, WORD=self.WORD) def get_completions(self, document, complete_event): return self.fuzzy_completer.get_completions(document, complete_event) _FuzzyMatch = namedtuple('_FuzzyMatch', 'match_length start_pos completion') prompt_toolkit-2.0.10/prompt_toolkit/completion/word_completer.py0000644000175100017510000000514113545407204027153 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from six import string_types from prompt_toolkit.completion import Completer, Completion __all__ = [ 'WordCompleter', ] class WordCompleter(Completer): """ Simple autocompletion on a list of words. :param words: List of words or callable that returns a list of words. :param ignore_case: If True, case-insensitive completion. :param meta_dict: Optional dict mapping words to their meta-text. (This should map strings to strings or formatted text.) :param WORD: When True, use WORD characters. :param sentence: When True, don't complete by comparing the word before the cursor, but by comparing all the text before the cursor. In this case, the list of words is just a list of strings, where each string can contain spaces. (Can not be used together with the WORD option.) :param match_middle: When True, match not only the start, but also in the middle of the word. :param pattern: Optional regex. When given, use this regex pattern instead of default one. """ def __init__(self, words, ignore_case=False, meta_dict=None, WORD=False, sentence=False, match_middle=False, pattern=None): assert not (WORD and sentence) assert callable(words) or all(isinstance(w, string_types) for w in words) self.words = words self.ignore_case = ignore_case self.meta_dict = meta_dict or {} self.WORD = WORD self.sentence = sentence self.match_middle = match_middle self.pattern = pattern def get_completions(self, document, complete_event): # Get list of words. words = self.words if callable(words): words = words() # Get word/text before cursor. if self.sentence: word_before_cursor = document.text_before_cursor else: word_before_cursor = document.get_word_before_cursor(WORD=self.WORD, pattern=self.pattern) if self.ignore_case: word_before_cursor = word_before_cursor.lower() def word_matches(word): """ True when the word before the cursor matches. """ if self.ignore_case: word = word.lower() if self.match_middle: return word_before_cursor in word else: return word.startswith(word_before_cursor) for a in words: if word_matches(a): display_meta = self.meta_dict.get(a, '') yield Completion(a, -len(word_before_cursor), display_meta=display_meta) prompt_toolkit-2.0.10/prompt_toolkit/contrib/0000755000175100017510000000000013545410361023040 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/contrib/__init__.py0000644000175100017510000000000013545407022025137 0ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/contrib/completers/0000755000175100017510000000000013545410361025215 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/contrib/completers/__init__.py0000644000175100017510000000011513545407204027325 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .system import SystemCompleter prompt_toolkit-2.0.10/prompt_toolkit/contrib/completers/system.py0000644000175100017510000000365213545407204027123 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.completion.filesystem import ( ExecutableCompleter, PathCompleter, ) from prompt_toolkit.contrib.regular_languages.compiler import compile from prompt_toolkit.contrib.regular_languages.completion import ( GrammarCompleter, ) __all__ = [ 'SystemCompleter', ] class SystemCompleter(GrammarCompleter): """ Completer for system commands. """ def __init__(self): # Compile grammar. g = compile( r""" # First we have an executable. (?P[^\s]+) # Ignore literals in between. ( \s+ ("[^"]*" | '[^']*' | [^'"]+ ) )* \s+ # Filename as parameters. ( (?P[^\s]+) | "(?P[^\s]+)" | '(?P[^\s]+)' ) """, escape_funcs={ 'double_quoted_filename': (lambda string: string.replace('"', '\\"')), 'single_quoted_filename': (lambda string: string.replace("'", "\\'")), }, unescape_funcs={ 'double_quoted_filename': (lambda string: string.replace('\\"', '"')), # XXX: not entirely correct. 'single_quoted_filename': (lambda string: string.replace("\\'", "'")), }) # Create GrammarCompleter super(SystemCompleter, self).__init__( g, { 'executable': ExecutableCompleter(), 'filename': PathCompleter(only_directories=False, expanduser=True), 'double_quoted_filename': PathCompleter(only_directories=False, expanduser=True), 'single_quoted_filename': PathCompleter(only_directories=False, expanduser=True), }) prompt_toolkit-2.0.10/prompt_toolkit/contrib/regular_languages/0000755000175100017510000000000013545410361026527 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/contrib/regular_languages/__init__.py0000644000175100017510000000632613545407022030647 0ustar jonathanjonathan00000000000000r""" Tool for expressing the grammar of an input as a regular language. ================================================================== The grammar for the input of many simple command line interfaces can be expressed by a regular language. Examples are PDB (the Python debugger); a simple (bash-like) shell with "pwd", "cd", "cat" and "ls" commands; arguments that you can pass to an executable; etc. It is possible to use regular expressions for validation and parsing of such a grammar. (More about regular languages: http://en.wikipedia.org/wiki/Regular_language) Example ------- Let's take the pwd/cd/cat/ls example. We want to have a shell that accepts these three commands. "cd" is followed by a quoted directory name and "cat" is followed by a quoted file name. (We allow quotes inside the filename when they're escaped with a backslash.) We could define the grammar using the following regular expression:: grammar = \s* ( pwd | ls | (cd \s+ " ([^"]|\.)+ ") | (cat \s+ " ([^"]|\.)+ ") ) \s* What can we do with this grammar? --------------------------------- - Syntax highlighting: We could use this for instance to give file names different colour. - Parse the result: .. We can extract the file names and commands by using a regular expression with named groups. - Input validation: .. Don't accept anything that does not match this grammar. When combined with a parser, we can also recursively do filename validation (and accept only existing files.) - Autocompletion: .... Each part of the grammar can have its own autocompleter. "cat" has to be completed using file names, while "cd" has to be completed using directory names. How does it work? ----------------- As a user of this library, you have to define the grammar of the input as a regular expression. The parts of this grammar where autocompletion, validation or any other processing is required need to be marked using a regex named group. Like ``(?P...)`` for instance. When the input is processed for validation (for instance), the regex will execute, the named group is captured, and the validator associated with this named group will test the captured string. There is one tricky bit: Ofter we operate on incomplete input (this is by definition the case for autocompletion) and we have to decide for the cursor position in which possible state the grammar it could be and in which way variables could be matched up to that point. To solve this problem, the compiler takes the original regular expression and translates it into a set of other regular expressions which each match prefixes of strings that would match the first expression. (We translate it into multiple expression, because we want to have each possible state the regex could be in -- in case there are several or-clauses with each different completers.) TODO: some examples of: - How to create a highlighter from this grammar. - How to create a validator from this grammar. - How to create an autocompleter from this grammar. - How to create a parser from this grammar. """ from .compiler import compile prompt_toolkit-2.0.10/prompt_toolkit/contrib/regular_languages/compiler.py0000644000175100017510000003643613545407204030731 0ustar jonathanjonathan00000000000000r""" Compiler for a regular grammar. Example usage:: # Create and compile grammar. p = compile('add \s+ (?P[^\s]+) \s+ (?P[^\s]+)') # Match input string. m = p.match('add 23 432') # Get variables. m.variables().get('var1') # Returns "23" m.variables().get('var2') # Returns "432" Partial matches are possible:: # Create and compile grammar. p = compile(''' # Operators with two arguments. ((?P[^\s]+) \s+ (?P[^\s]+) \s+ (?P[^\s]+)) | # Operators with only one arguments. ((?P[^\s]+) \s+ (?P[^\s]+)) ''') # Match partial input string. m = p.match_prefix('add 23') # Get variables. (Notice that both operator1 and operator2 contain the # value "add".) This is because our input is incomplete, and we don't know # yet in which rule of the regex we we'll end up. It could also be that # `operator1` and `operator2` have a different autocompleter and we want to # call all possible autocompleters that would result in valid input.) m.variables().get('var1') # Returns "23" m.variables().get('operator1') # Returns "add" m.variables().get('operator2') # Returns "add" """ from __future__ import unicode_literals import re from six.moves import range from .regex_parser import ( Any, Lookahead, Regex, Repeat, Sequence, Variable, parse_regex, tokenize_regex, ) __all__ = [ 'compile', ] # Name of the named group in the regex, matching trailing input. # (Trailing input is when the input contains characters after the end of the # expression has been matched.) _INVALID_TRAILING_INPUT = 'invalid_trailing' class _CompiledGrammar(object): """ Compiles a grammar. This will take the parse tree of a regular expression and compile the grammar. :param root_node: :class~`.regex_parser.Node` instance. :param escape_funcs: `dict` mapping variable names to escape callables. :param unescape_funcs: `dict` mapping variable names to unescape callables. """ def __init__(self, root_node, escape_funcs=None, unescape_funcs=None): self.root_node = root_node self.escape_funcs = escape_funcs or {} self.unescape_funcs = unescape_funcs or {} #: Dictionary that will map the regex names to Node instances. self._group_names_to_nodes = {} # Maps regex group names to varnames. counter = [0] def create_group_func(node): name = 'n%s' % counter[0] self._group_names_to_nodes[name] = node.varname counter[0] += 1 return name # Compile regex strings. self._re_pattern = '^%s$' % self._transform(root_node, create_group_func) self._re_prefix_patterns = list(self._transform_prefix(root_node, create_group_func)) # Compile the regex itself. flags = re.DOTALL # Note that we don't need re.MULTILINE! (^ and $ # still represent the start and end of input text.) self._re = re.compile(self._re_pattern, flags) self._re_prefix = [re.compile(t, flags) for t in self._re_prefix_patterns] # We compile one more set of regexes, similar to `_re_prefix`, but accept any trailing # input. This will ensure that we can still highlight the input correctly, even when the # input contains some additional characters at the end that don't match the grammar.) self._re_prefix_with_trailing_input = [ re.compile(r'(?:%s)(?P<%s>.*?)$' % (t.rstrip('$'), _INVALID_TRAILING_INPUT), flags) for t in self._re_prefix_patterns] def escape(self, varname, value): """ Escape `value` to fit in the place of this variable into the grammar. """ f = self.escape_funcs.get(varname) return f(value) if f else value def unescape(self, varname, value): """ Unescape `value`. """ f = self.unescape_funcs.get(varname) return f(value) if f else value @classmethod def _transform(cls, root_node, create_group_func): """ Turn a :class:`Node` object into a regular expression. :param root_node: The :class:`Node` instance for which we generate the grammar. :param create_group_func: A callable which takes a `Node` and returns the next free name for this node. """ def transform(node): # Turn `Any` into an OR. if isinstance(node, Any): return '(?:%s)' % '|'.join(transform(c) for c in node.children) # Concatenate a `Sequence` elif isinstance(node, Sequence): return ''.join(transform(c) for c in node.children) # For Regex and Lookahead nodes, just insert them literally. elif isinstance(node, Regex): return node.regex elif isinstance(node, Lookahead): before = ('(?!' if node.negative else '(=') return before + transform(node.childnode) + ')' # A `Variable` wraps the children into a named group. elif isinstance(node, Variable): return '(?P<%s>%s)' % (create_group_func(node), transform(node.childnode)) # `Repeat`. elif isinstance(node, Repeat): return '(?:%s){%i,%s}%s' % ( transform(node.childnode), node.min_repeat, ('' if node.max_repeat is None else str(node.max_repeat)), ('' if node.greedy else '?') ) else: raise TypeError('Got %r' % (node, )) return transform(root_node) @classmethod def _transform_prefix(cls, root_node, create_group_func): """ Yield all the regular expressions matching a prefix of the grammar defined by the `Node` instance. This can yield multiple expressions, because in the case of on OR operation in the grammar, we can have another outcome depending on which clause would appear first. E.g. "(A|B)C" is not the same as "(B|A)C" because the regex engine is lazy and takes the first match. However, because we the current input is actually a prefix of the grammar which might not yet contain the data for "C", we need to know both intermediate states, in order to call the appropriate autocompletion for both cases. :param root_node: The :class:`Node` instance for which we generate the grammar. :param create_group_func: A callable which takes a `Node` and returns the next free name for this node. """ def transform(node): # Generate regexes for all permutations of this OR. Each node # should be in front once. if isinstance(node, Any): for c in node.children: for r in transform(c): yield '(?:%s)?' % r # For a sequence. We can either have a match for the sequence # of all the children, or for an exact match of the first X # children, followed by a partial match of the next children. elif isinstance(node, Sequence): for i in range(len(node.children)): a = [cls._transform(c, create_group_func) for c in node.children[:i]] for c in transform(node.children[i]): yield '(?:%s)' % (''.join(a) + c) elif isinstance(node, Regex): yield '(?:%s)?' % node.regex elif isinstance(node, Lookahead): if node.negative: yield '(?!%s)' % cls._transform(node.childnode, create_group_func) else: # Not sure what the correct semantics are in this case. # (Probably it's not worth implementing this.) raise Exception('Positive lookahead not yet supported.') elif isinstance(node, Variable): # (Note that we should not append a '?' here. the 'transform' # method will already recursively do that.) for c in transform(node.childnode): yield '(?P<%s>%s)' % (create_group_func(node), c) elif isinstance(node, Repeat): # If we have a repetition of 8 times. That would mean that the # current input could have for instance 7 times a complete # match, followed by a partial match. prefix = cls._transform(node.childnode, create_group_func) for c in transform(node.childnode): if node.max_repeat: repeat_sign = '{,%i}' % (node.max_repeat - 1) else: repeat_sign = '*' yield '(?:%s)%s%s(?:%s)?' % ( prefix, repeat_sign, ('' if node.greedy else '?'), c) else: raise TypeError('Got %r' % node) for r in transform(root_node): yield '^%s$' % r def match(self, string): """ Match the string with the grammar. Returns a :class:`Match` instance or `None` when the input doesn't match the grammar. :param string: The input string. """ m = self._re.match(string) if m: return Match(string, [(self._re, m)], self._group_names_to_nodes, self.unescape_funcs) def match_prefix(self, string): """ Do a partial match of the string with the grammar. The returned :class:`Match` instance can contain multiple representations of the match. This will never return `None`. If it doesn't match at all, the "trailing input" part will capture all of the input. :param string: The input string. """ # First try to match using `_re_prefix`. If nothing is found, use the patterns that # also accept trailing characters. for patterns in [self._re_prefix, self._re_prefix_with_trailing_input]: matches = [(r, r.match(string)) for r in patterns] matches = [(r, m) for r, m in matches if m] if matches != []: return Match(string, matches, self._group_names_to_nodes, self.unescape_funcs) class Match(object): """ :param string: The input string. :param re_matches: List of (compiled_re_pattern, re_match) tuples. :param group_names_to_nodes: Dictionary mapping all the re group names to the matching Node instances. """ def __init__(self, string, re_matches, group_names_to_nodes, unescape_funcs): self.string = string self._re_matches = re_matches self._group_names_to_nodes = group_names_to_nodes self._unescape_funcs = unescape_funcs def _nodes_to_regs(self): """ Return a list of (varname, reg) tuples. """ def get_tuples(): for r, re_match in self._re_matches: for group_name, group_index in r.groupindex.items(): if group_name != _INVALID_TRAILING_INPUT: reg = re_match.regs[group_index] node = self._group_names_to_nodes[group_name] yield (node, reg) return list(get_tuples()) def _nodes_to_values(self): """ Returns list of list of (Node, string_value) tuples. """ def is_none(slice): return slice[0] == -1 and slice[1] == -1 def get(slice): return self.string[slice[0]:slice[1]] return [(varname, get(slice), slice) for varname, slice in self._nodes_to_regs() if not is_none(slice)] def _unescape(self, varname, value): unwrapper = self._unescape_funcs.get(varname) return unwrapper(value) if unwrapper else value def variables(self): """ Returns :class:`Variables` instance. """ return Variables([(k, self._unescape(k, v), sl) for k, v, sl in self._nodes_to_values()]) def trailing_input(self): """ Get the `MatchVariable` instance, representing trailing input, if there is any. "Trailing input" is input at the end that does not match the grammar anymore, but when this is removed from the end of the input, the input would be a valid string. """ slices = [] # Find all regex group for the name _INVALID_TRAILING_INPUT. for r, re_match in self._re_matches: for group_name, group_index in r.groupindex.items(): if group_name == _INVALID_TRAILING_INPUT: slices.append(re_match.regs[group_index]) # Take the smallest part. (Smaller trailing text means that a larger input has # been matched, so that is better.) if slices: slice = [max(i[0] for i in slices), max(i[1] for i in slices)] value = self.string[slice[0]:slice[1]] return MatchVariable('', value, slice) def end_nodes(self): """ Yields `MatchVariable` instances for all the nodes having their end position at the end of the input string. """ for varname, reg in self._nodes_to_regs(): # If this part goes until the end of the input string. if reg[1] == len(self.string): value = self._unescape(varname, self.string[reg[0]: reg[1]]) yield MatchVariable(varname, value, (reg[0], reg[1])) class Variables(object): def __init__(self, tuples): #: List of (varname, value, slice) tuples. self._tuples = tuples def __repr__(self): return '%s(%s)' % ( self.__class__.__name__, ', '.join('%s=%r' % (k, v) for k, v, _ in self._tuples)) def get(self, key, default=None): items = self.getall(key) return items[0] if items else default def getall(self, key): return [v for k, v, _ in self._tuples if k == key] def __getitem__(self, key): return self.get(key) def __iter__(self): """ Yield `MatchVariable` instances. """ for varname, value, slice in self._tuples: yield MatchVariable(varname, value, slice) class MatchVariable(object): """ Represents a match of a variable in the grammar. :param varname: (string) Name of the variable. :param value: (string) Value of this variable. :param slice: (start, stop) tuple, indicating the position of this variable in the input string. """ def __init__(self, varname, value, slice): self.varname = varname self.value = value self.slice = slice self.start = self.slice[0] self.stop = self.slice[1] def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.varname, self.value) def compile(expression, escape_funcs=None, unescape_funcs=None): """ Compile grammar (given as regex string), returning a `CompiledGrammar` instance. """ return _compile_from_parse_tree( parse_regex(tokenize_regex(expression)), escape_funcs=escape_funcs, unescape_funcs=unescape_funcs) def _compile_from_parse_tree(root_node, *a, **kw): """ Compile grammar (given as parse tree), returning a `CompiledGrammar` instance. """ return _CompiledGrammar(root_node, *a, **kw) prompt_toolkit-2.0.10/prompt_toolkit/contrib/regular_languages/completion.py0000644000175100017510000000563713545407204031267 0ustar jonathanjonathan00000000000000""" Completer for a regular grammar. """ from __future__ import unicode_literals from prompt_toolkit.completion import Completer, Completion from prompt_toolkit.document import Document from .compiler import _CompiledGrammar __all__ = [ 'GrammarCompleter', ] class GrammarCompleter(Completer): """ Completer which can be used for autocompletion according to variables in the grammar. Each variable can have a different autocompleter. :param compiled_grammar: `GrammarCompleter` instance. :param completers: `dict` mapping variable names of the grammar to the `Completer` instances to be used for each variable. """ def __init__(self, compiled_grammar, completers): assert isinstance(compiled_grammar, _CompiledGrammar) assert isinstance(completers, dict) self.compiled_grammar = compiled_grammar self.completers = completers def get_completions(self, document, complete_event): m = self.compiled_grammar.match_prefix(document.text_before_cursor) if m: completions = self._remove_duplicates( self._get_completions_for_match(m, complete_event)) for c in completions: yield c def _get_completions_for_match(self, match, complete_event): """ Yield all the possible completions for this input string. (The completer assumes that the cursor position was at the end of the input string.) """ for match_variable in match.end_nodes(): varname = match_variable.varname start = match_variable.start completer = self.completers.get(varname) if completer: text = match_variable.value # Unwrap text. unwrapped_text = self.compiled_grammar.unescape(varname, text) # Create a document, for the completions API (text/cursor_position) document = Document(unwrapped_text, len(unwrapped_text)) # Call completer for completion in completer.get_completions(document, complete_event): new_text = unwrapped_text[:len(text) + completion.start_position] + completion.text # Wrap again. yield Completion( text=self.compiled_grammar.escape(varname, new_text), start_position=start - len(match.string), display=completion.display, display_meta=completion.display_meta) def _remove_duplicates(self, items): """ Remove duplicates, while keeping the order. (Sometimes we have duplicates, because the there several matches of the same grammar, each yielding similar completions.) """ result = [] for i in items: if i not in result: result.append(i) return result prompt_toolkit-2.0.10/prompt_toolkit/contrib/regular_languages/lexer.py0000644000175100017510000000642013545407204030224 0ustar jonathanjonathan00000000000000""" `GrammarLexer` is compatible with other lexers and can be used to highlight the input using a regular grammar with annotations. """ from __future__ import unicode_literals import six from six.moves import range from prompt_toolkit.document import Document from prompt_toolkit.formatted_text.utils import split_lines from prompt_toolkit.lexers import Lexer from .compiler import _CompiledGrammar __all__ = [ 'GrammarLexer', ] class GrammarLexer(Lexer): """ Lexer which can be used for highlighting of fragments according to variables in the grammar. (It does not actual lexing of the string, but it exposes an API, compatible with the Pygments lexer class.) :param compiled_grammar: Grammar as returned by the `compile()` function. :param lexers: Dictionary mapping variable names of the regular grammar to the lexers that should be used for this part. (This can call other lexers recursively.) If you wish a part of the grammar to just get one fragment, use a `prompt_toolkit.lexers.SimpleLexer`. """ def __init__(self, compiled_grammar, default_style='', lexers=None): assert isinstance(compiled_grammar, _CompiledGrammar) assert isinstance(default_style, six.text_type) assert lexers is None or all(isinstance(v, Lexer) for k, v in lexers.items()) assert lexers is None or isinstance(lexers, dict) self.compiled_grammar = compiled_grammar self.default_style = default_style self.lexers = lexers or {} def _get_text_fragments(self, text): m = self.compiled_grammar.match_prefix(text) if m: characters = [[self.default_style, c] for c in text] for v in m.variables(): # If we have a `Lexer` instance for this part of the input. # Tokenize recursively and apply tokens. lexer = self.lexers.get(v.varname) if lexer: document = Document(text[v.start:v.stop]) lexer_tokens_for_line = lexer.lex_document(document) text_fragments = [] for i in range(len(document.lines)): text_fragments.extend(lexer_tokens_for_line(i)) text_fragments.append(('', '\n')) if text_fragments: text_fragments.pop() i = v.start for t, s in text_fragments: for c in s: if characters[i][0] == self.default_style: characters[i][0] = t i += 1 # Highlight trailing input. trailing_input = m.trailing_input() if trailing_input: for i in range(trailing_input.start, trailing_input.stop): characters[i][0] = 'class:trailing-input' return characters else: return [('', text)] def lex_document(self, document): lines = list(split_lines(self._get_text_fragments(document.text))) def get_line(lineno): try: return lines[lineno] except IndexError: return [] return get_line prompt_toolkit-2.0.10/prompt_toolkit/contrib/regular_languages/regex_parser.py0000644000175100017510000001614313545407204031576 0ustar jonathanjonathan00000000000000""" Parser for parsing a regular expression. Take a string representing a regular expression and return the root node of its parse tree. usage:: root_node = parse_regex('(hello|world)') Remarks: - The regex parser processes multiline, it ignores all whitespace and supports multiple named groups with the same name and #-style comments. Limitations: - Lookahead is not supported. """ from __future__ import unicode_literals import re __all__ = [ 'Repeat', 'Variable', 'Regex', 'Lookahead', 'tokenize_regex', 'parse_regex', ] class Node(object): """ Base class for all the grammar nodes. (You don't initialize this one.) """ def __add__(self, other_node): return Sequence([self, other_node]) def __or__(self, other_node): return Any([self, other_node]) class Any(Node): """ Union operation (OR operation) between several grammars. You don't initialize this yourself, but it's a result of a "Grammar1 | Grammar2" operation. """ def __init__(self, children): self.children = children def __or__(self, other_node): return Any(self.children + [other_node]) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.children) class Sequence(Node): """ Concatenation operation of several grammars. You don't initialize this yourself, but it's a result of a "Grammar1 + Grammar2" operation. """ def __init__(self, children): self.children = children def __add__(self, other_node): return Sequence(self.children + [other_node]) def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.children) class Regex(Node): """ Regular expression. """ def __init__(self, regex): re.compile(regex) # Validate self.regex = regex def __repr__(self): return '%s(/%s/)' % (self.__class__.__name__, self.regex) class Lookahead(Node): """ Lookahead expression. """ def __init__(self, childnode, negative=False): self.childnode = childnode self.negative = negative def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.childnode) class Variable(Node): """ Mark a variable in the regular grammar. This will be translated into a named group. Each variable can have his own completer, validator, etc.. :param childnode: The grammar which is wrapped inside this variable. :param varname: String. """ def __init__(self, childnode, varname=None): self.childnode = childnode self.varname = varname def __repr__(self): return '%s(childnode=%r, varname=%r)' % ( self.__class__.__name__, self.childnode, self.varname) class Repeat(Node): def __init__(self, childnode, min_repeat=0, max_repeat=None, greedy=True): self.childnode = childnode self.min_repeat = min_repeat self.max_repeat = max_repeat self.greedy = greedy def __repr__(self): return '%s(childnode=%r)' % (self.__class__.__name__, self.childnode) def tokenize_regex(input): """ Takes a string, representing a regular expression as input, and tokenizes it. :param input: string, representing a regular expression. :returns: List of tokens. """ # Regular expression for tokenizing other regular expressions. p = re.compile(r'''^( \(\?P\<[a-zA-Z0-9_-]+\> | # Start of named group. \(\?#[^)]*\) | # Comment \(\?= | # Start of lookahead assertion \(\?! | # Start of negative lookahead assertion \(\?<= | # If preceded by. \(\?< | # If not preceded by. \(?: | # Start of group. (non capturing.) \( | # Start of group. \(?[iLmsux] | # Flags. \(?P=[a-zA-Z]+\) | # Back reference to named group \) | # End of group. \{[^{}]*\} | # Repetition \*\? | \+\? | \?\?\ | # Non greedy repetition. \* | \+ | \? | # Repetition \#.*\n | # Comment \\. | # Character group. \[ ( [^\]\\] | \\.)* \] | [^(){}] | . )''', re.VERBOSE) tokens = [] while input: m = p.match(input) if m: token, input = input[:m.end()], input[m.end():] if not token.isspace(): tokens.append(token) else: raise Exception('Could not tokenize input regex.') return tokens def parse_regex(regex_tokens): """ Takes a list of tokens from the tokenizer, and returns a parse tree. """ # We add a closing brace because that represents the final pop of the stack. tokens = [')'] + regex_tokens[::-1] def wrap(lst): """ Turn list into sequence when it contains several items. """ if len(lst) == 1: return lst[0] else: return Sequence(lst) def _parse(): or_list = [] result = [] def wrapped_result(): if or_list == []: return wrap(result) else: or_list.append(result) return Any([wrap(i) for i in or_list]) while tokens: t = tokens.pop() if t.startswith('(?P<'): variable = Variable(_parse(), varname=t[4:-1]) result.append(variable) elif t in ('*', '*?'): greedy = (t == '*') result[-1] = Repeat(result[-1], greedy=greedy) elif t in ('+', '+?'): greedy = (t == '+') result[-1] = Repeat(result[-1], min_repeat=1, greedy=greedy) elif t in ('?', '??'): if result == []: raise Exception('Nothing to repeat.' + repr(tokens)) else: greedy = (t == '?') result[-1] = Repeat(result[-1], min_repeat=0, max_repeat=1, greedy=greedy) elif t == '|': or_list.append(result) result = [] elif t in ('(', '(?:'): result.append(_parse()) elif t == '(?!': result.append(Lookahead(_parse(), negative=True)) elif t == '(?=': result.append(Lookahead(_parse(), negative=False)) elif t == ')': return wrapped_result() elif t.startswith('#'): pass elif t.startswith('{'): # TODO: implement! raise Exception('{}-style repetition not yet supported' % t) elif t.startswith('(?'): raise Exception('%r not supported' % t) elif t.isspace(): pass else: result.append(Regex(t)) raise Exception("Expecting ')' token") result = _parse() if len(tokens) != 0: raise Exception("Unmatched parentheses.") else: return result prompt_toolkit-2.0.10/prompt_toolkit/contrib/regular_languages/validation.py0000644000175100017510000000401413545407204031234 0ustar jonathanjonathan00000000000000""" Validator for a regular language. """ from __future__ import unicode_literals from prompt_toolkit.document import Document from prompt_toolkit.validation import ValidationError, Validator from .compiler import _CompiledGrammar __all__ = [ 'GrammarValidator', ] class GrammarValidator(Validator): """ Validator which can be used for validation according to variables in the grammar. Each variable can have its own validator. :param compiled_grammar: `GrammarCompleter` instance. :param validators: `dict` mapping variable names of the grammar to the `Validator` instances to be used for each variable. """ def __init__(self, compiled_grammar, validators): assert isinstance(compiled_grammar, _CompiledGrammar) assert isinstance(validators, dict) self.compiled_grammar = compiled_grammar self.validators = validators def validate(self, document): # Parse input document. # We use `match`, not `match_prefix`, because for validation, we want # the actual, unambiguous interpretation of the input. m = self.compiled_grammar.match(document.text) if m: for v in m.variables(): validator = self.validators.get(v.varname) if validator: # Unescape text. unwrapped_text = self.compiled_grammar.unescape(v.varname, v.value) # Create a document, for the completions API (text/cursor_position) inner_document = Document(unwrapped_text, len(unwrapped_text)) try: validator.validate(inner_document) except ValidationError as e: raise ValidationError( cursor_position=v.start + e.cursor_position, message=e.message) else: raise ValidationError(cursor_position=len(document.text), message='Invalid command') prompt_toolkit-2.0.10/prompt_toolkit/contrib/telnet/0000755000175100017510000000000013545410361024333 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/contrib/telnet/__init__.py0000644000175100017510000000010413545407022026437 0ustar jonathanjonathan00000000000000from .server import TelnetServer __all__ = [ 'TelnetServer', ] prompt_toolkit-2.0.10/prompt_toolkit/contrib/telnet/log.py0000644000175100017510000000025313545407204025470 0ustar jonathanjonathan00000000000000""" Python logger for the telnet server. """ from __future__ import unicode_literals import logging logger = logging.getLogger(__package__) __all__ = [ 'logger', ] prompt_toolkit-2.0.10/prompt_toolkit/contrib/telnet/protocol.py0000644000175100017510000001142513545407204026553 0ustar jonathanjonathan00000000000000""" Parser for the Telnet protocol. (Not a complete implementation of the telnet specification, but sufficient for a command line interface.) Inspired by `Twisted.conch.telnet`. """ from __future__ import unicode_literals import struct from six import binary_type, int2byte, iterbytes from .log import logger __all__ = [ 'TelnetProtocolParser', ] # Telnet constants. NOP = int2byte(0) SGA = int2byte(3) IAC = int2byte(255) DO = int2byte(253) DONT = int2byte(254) LINEMODE = int2byte(34) SB = int2byte(250) WILL = int2byte(251) WONT = int2byte(252) MODE = int2byte(1) SE = int2byte(240) ECHO = int2byte(1) NAWS = int2byte(31) LINEMODE = int2byte(34) SUPPRESS_GO_AHEAD = int2byte(3) DM = int2byte(242) BRK = int2byte(243) IP = int2byte(244) AO = int2byte(245) AYT = int2byte(246) EC = int2byte(247) EL = int2byte(248) GA = int2byte(249) class TelnetProtocolParser(object): """ Parser for the Telnet protocol. Usage:: def data_received(data): print(data) def size_received(rows, columns): print(rows, columns) p = TelnetProtocolParser(data_received, size_received) p.feed(binary_data) """ def __init__(self, data_received_callback, size_received_callback): self.data_received_callback = data_received_callback self.size_received_callback = size_received_callback self._parser = self._parse_coroutine() self._parser.send(None) def received_data(self, data): self.data_received_callback(data) def do_received(self, data): """ Received telnet DO command. """ logger.info('DO %r', data) def dont_received(self, data): """ Received telnet DONT command. """ logger.info('DONT %r', data) def will_received(self, data): """ Received telnet WILL command. """ logger.info('WILL %r', data) def wont_received(self, data): """ Received telnet WONT command. """ logger.info('WONT %r', data) def command_received(self, command, data): if command == DO: self.do_received(data) elif command == DONT: self.dont_received(data) elif command == WILL: self.will_received(data) elif command == WONT: self.wont_received(data) else: logger.info('command received %r %r', command, data) def naws(self, data): """ Received NAWS. (Window dimensions.) """ if len(data) == 4: # NOTE: the first parameter of struct.unpack should be # a 'str' object. Both on Py2/py3. This crashes on OSX # otherwise. columns, rows = struct.unpack(str('!HH'), data) self.size_received_callback(rows, columns) else: logger.warning('Wrong number of NAWS bytes') def negotiate(self, data): """ Got negotiate data. """ command, payload = data[0:1], data[1:] assert isinstance(command, bytes) if command == NAWS: self.naws(payload) else: logger.info('Negotiate (%r got bytes)', len(data)) def _parse_coroutine(self): """ Parser state machine. Every 'yield' expression returns the next byte. """ while True: d = yield if d == int2byte(0): pass # NOP # Go to state escaped. elif d == IAC: d2 = yield if d2 == IAC: self.received_data(d2) # Handle simple commands. elif d2 in (NOP, DM, BRK, IP, AO, AYT, EC, EL, GA): self.command_received(d2, None) # Handle IAC-[DO/DONT/WILL/WONT] commands. elif d2 in (DO, DONT, WILL, WONT): d3 = yield self.command_received(d2, d3) # Subnegotiation elif d2 == SB: # Consume everything until next IAC-SE data = [] while True: d3 = yield if d3 == IAC: d4 = yield if d4 == SE: break else: data.append(d4) else: data.append(d3) self.negotiate(b''.join(data)) else: self.received_data(d) def feed(self, data): """ Feed data to the parser. """ assert isinstance(data, binary_type) for b in iterbytes(data): self._parser.send(int2byte(b)) prompt_toolkit-2.0.10/prompt_toolkit/contrib/telnet/server.py0000644000175100017510000002166413545407204026226 0ustar jonathanjonathan00000000000000""" Telnet server. """ from __future__ import unicode_literals import inspect import socket import sys from six import binary_type, int2byte, text_type from prompt_toolkit.application.current import get_app from prompt_toolkit.application.run_in_terminal import run_in_terminal from prompt_toolkit.eventloop import ( From, Future, ensure_future, get_event_loop, ) from prompt_toolkit.eventloop.context import context from prompt_toolkit.formatted_text import to_formatted_text from prompt_toolkit.input.defaults import set_default_input from prompt_toolkit.input.posix_pipe import PosixPipeInput from prompt_toolkit.layout.screen import Size from prompt_toolkit.output.defaults import set_default_output from prompt_toolkit.output.vt100 import Vt100_Output from prompt_toolkit.renderer import \ print_formatted_text as print_formatted_text from prompt_toolkit.styles import DummyStyle from .log import logger from .protocol import ( DO, ECHO, IAC, LINEMODE, MODE, NAWS, SB, SE, SUPPRESS_GO_AHEAD, WILL, TelnetProtocolParser, ) __all__ = [ 'TelnetServer', ] def _initialize_telnet(connection): logger.info('Initializing telnet connection') # Iac Do Linemode connection.send(IAC + DO + LINEMODE) # Suppress Go Ahead. (This seems important for Putty to do correct echoing.) # This will allow bi-directional operation. connection.send(IAC + WILL + SUPPRESS_GO_AHEAD) # Iac sb connection.send(IAC + SB + LINEMODE + MODE + int2byte(0) + IAC + SE) # IAC Will Echo connection.send(IAC + WILL + ECHO) # Negotiate window size connection.send(IAC + DO + NAWS) def _is_coroutine(func): if sys.version_info > (3, 5, 0): return inspect.iscoroutine(func) return False class _ConnectionStdout(object): """ Wrapper around socket which provides `write` and `flush` methods for the Vt100_Output output. """ def __init__(self, connection, encoding): self._encoding = encoding self._connection = connection self._buffer = [] def write(self, data): assert isinstance(data, text_type) self._buffer.append(data.encode(self._encoding)) self.flush() def flush(self): try: self._connection.send(b''.join(self._buffer)) except socket.error as e: logger.warning("Couldn't send data over socket: %s" % e) self._buffer = [] class TelnetConnection(object): """ Class that represents one Telnet connection. """ def __init__(self, conn, addr, interact, server, encoding, style): assert isinstance(addr, tuple) # (addr, port) tuple assert callable(interact) assert isinstance(server, TelnetServer) assert isinstance(encoding, text_type) # e.g. 'utf-8' self.conn = conn self.addr = addr self.interact = interact self.server = server self.encoding = encoding self.style = style self._closed = False # Execution context. self._context_id = None # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create input. self.vt100_input = PosixPipeInput() # Create output. def get_size(): return self.size self.stdout = _ConnectionStdout(conn, encoding=encoding) self.vt100_output = Vt100_Output( self.stdout, get_size, write_binary=False) def data_received(data): """ TelnetProtocolParser 'data_received' callback """ assert isinstance(data, binary_type) self.vt100_input.send_bytes(data) def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) get_app()._on_resize() self.parser = TelnetProtocolParser(data_received, size_received) def run_application(self): """ Run application. """ def handle_incoming_data(): data = self.conn.recv(1024) if data: self.feed(data) else: # Connection closed by client. logger.info('Connection closed by client. %r %r' % self.addr) self.close() def run(): with context() as ctx_id: self._context_id = ctx_id # Set input/output for all application running in this context. set_default_input(self.vt100_input) set_default_output(self.vt100_output) # Add reader. loop = get_event_loop() loop.add_reader(self.conn, handle_incoming_data) try: obj = self.interact(self) if _is_coroutine(obj): # Got an asyncio coroutine. import asyncio f = asyncio.ensure_future(obj) yield From(Future.from_asyncio_future(f)) else: # Got a prompt_toolkit coroutine. yield From(obj) except Exception as e: print('Got %s' % type(e).__name__, e) import traceback; traceback.print_exc() raise finally: self.close() return ensure_future(run()) def feed(self, data): """ Handler for incoming data. (Called by TelnetServer.) """ assert isinstance(data, binary_type) self.parser.feed(data) def close(self): """ Closed by client. """ if not self._closed: self._closed = True self.vt100_input.close() get_event_loop().remove_reader(self.conn) self.conn.close() def send(self, formatted_text): """ Send text to the client. """ formatted_text = to_formatted_text(formatted_text) print_formatted_text(self.vt100_output, formatted_text, self.style or DummyStyle()) def send_above_prompt(self, formatted_text): """ Send text to the client. This is asynchronous, returns a `Future`. """ formatted_text = to_formatted_text(formatted_text) return self._run_in_terminal(lambda: self.send(formatted_text)) def _run_in_terminal(self, func): # Make sure that when an application was active for this connection, # that we print the text above the application. with context(self._context_id): return run_in_terminal(func) def erase_screen(self): """ Erase the screen and move the cursor to the top. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush() class TelnetServer(object): """ Telnet server implementation. """ def __init__(self, host='127.0.0.1', port=23, interact=None, encoding='utf-8', style=None): assert isinstance(host, text_type) assert isinstance(port, int) assert callable(interact) assert isinstance(encoding, text_type) self.host = host self.port = port self.interact = interact self.encoding = encoding self.style = style self.connections = set() self._listen_socket = None @classmethod def _create_socket(cls, host, port): # Create and bind socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) s.listen(4) return s def start(self): """ Start the telnet server. Don't forget to call `loop.run_forever()` after doing this. """ self._listen_socket = self._create_socket(self.host, self.port) logger.info('Listening for telnet connections on %s port %r', self.host, self.port) get_event_loop().add_reader(self._listen_socket, self._accept) def stop(self): if self._listen_socket: self._listen_socket.close() def _accept(self): """ Accept new incoming connection. """ conn, addr = self._listen_socket.accept() logger.info('New connection %r %r', *addr) connection = TelnetConnection( conn, addr, self.interact, self, encoding=self.encoding, style=self.style) self.connections.add(connection) # Run application for this connection. def run(): logger.info('Starting interaction %r %r', *addr) try: yield From(connection.run_application()) except Exception as e: print(e) finally: self.connections.remove(connection) logger.info('Stopping interaction %r %r', *addr) ensure_future(run()) prompt_toolkit-2.0.10/prompt_toolkit/document.py0000644000175100017510000011177113545407204023602 0ustar jonathanjonathan00000000000000""" The `Document` that implements all the text operations/querying. """ from __future__ import unicode_literals import bisect import re import string import weakref import six from six.moves import map, range from .clipboard import ClipboardData from .filters import vi_mode from .selection import PasteMode, SelectionState, SelectionType __all__ = [ 'Document', ] # Regex for finding "words" in documents. (We consider a group of alnum # characters a word, but also a group of special characters a word, as long as # it doesn't contain a space.) # (This is a 'word' in Vi.) _FIND_WORD_RE = re.compile(r'([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)') _FIND_CURRENT_WORD_RE = re.compile(r'^([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)') _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^(([a-zA-Z0-9_]+|[^a-zA-Z0-9_\s]+)\s*)') # Regex for finding "WORDS" in documents. # (This is a 'WORD in Vi.) _FIND_BIG_WORD_RE = re.compile(r'([^\s]+)') _FIND_CURRENT_BIG_WORD_RE = re.compile(r'^([^\s]+)') _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE = re.compile(r'^([^\s]+\s*)') # Share the Document._cache between all Document instances. # (Document instances are considered immutable. That means that if another # `Document` is constructed with the same text, it should have the same # `_DocumentCache`.) _text_to_document_cache = weakref.WeakValueDictionary() # Maps document.text to DocumentCache instance. class _ImmutableLineList(list): """ Some protection for our 'lines' list, which is assumed to be immutable in the cache. (Useful for detecting obvious bugs.) """ def _error(self, *a, **kw): raise NotImplementedError('Attempt to modify an immutable list.') __setitem__ = _error append = _error clear = _error extend = _error insert = _error pop = _error remove = _error reverse = _error sort = _error class _DocumentCache(object): def __init__(self): #: List of lines for the Document text. self.lines = None #: List of index positions, pointing to the start of all the lines. self.line_indexes = None class Document(object): """ This is a immutable class around the text and cursor position, and contains methods for querying this data, e.g. to give the text before the cursor. This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer` object, and accessed as the `document` property of that class. :param text: string :param cursor_position: int :param selection: :class:`.SelectionState` """ __slots__ = ('_text', '_cursor_position', '_selection', '_cache') def __init__(self, text='', cursor_position=None, selection=None): assert isinstance(text, six.text_type), 'Got %r' % text assert selection is None or isinstance(selection, SelectionState) # Check cursor position. It can also be right after the end. (Where we # insert text.) assert cursor_position is None or cursor_position <= len(text), AssertionError( 'cursor_position=%r, len_text=%r' % (cursor_position, len(text))) # By default, if no cursor position was given, make sure to put the # cursor position is at the end of the document. This is what makes # sense in most places. if cursor_position is None: cursor_position = len(text) # Keep these attributes private. A `Document` really has to be # considered to be immutable, because otherwise the caching will break # things. Because of that, we wrap these into read-only properties. self._text = text self._cursor_position = cursor_position self._selection = selection # Cache for lines/indexes. (Shared with other Document instances that # contain the same text. try: self._cache = _text_to_document_cache[self.text] except KeyError: self._cache = _DocumentCache() _text_to_document_cache[self.text] = self._cache # XX: For some reason, above, we can't use 'WeakValueDictionary.setdefault'. # This fails in Pypy3. `self._cache` becomes None, because that's what # 'setdefault' returns. # self._cache = _text_to_document_cache.setdefault(self.text, _DocumentCache()) # assert self._cache def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.text, self.cursor_position) def __eq__(self, other): assert isinstance(other, Document) return (self.text == other.text and self.cursor_position == other.cursor_position and self.selection == other.selection) @property def text(self): " The document text. " return self._text @property def cursor_position(self): " The document cursor position. " return self._cursor_position @property def selection(self): " :class:`.SelectionState` object. " return self._selection @property def current_char(self): """ Return character under cursor or an empty string. """ return self._get_char_relative_to_cursor(0) or '' @property def char_before_cursor(self): """ Return character before the cursor or an empty string. """ return self._get_char_relative_to_cursor(-1) or '' @property def text_before_cursor(self): return self.text[:self.cursor_position:] @property def text_after_cursor(self): return self.text[self.cursor_position:] @property def current_line_before_cursor(self): """ Text from the start of the line until the cursor. """ _, _, text = self.text_before_cursor.rpartition('\n') return text @property def current_line_after_cursor(self): """ Text from the cursor until the end of the line. """ text, _, _ = self.text_after_cursor.partition('\n') return text @property def lines(self): """ Array of all the lines. """ # Cache, because this one is reused very often. if self._cache.lines is None: self._cache.lines = _ImmutableLineList(self.text.split('\n')) return self._cache.lines @property def _line_start_indexes(self): """ Array pointing to the start indexes of all the lines. """ # Cache, because this is often reused. (If it is used, it's often used # many times. And this has to be fast for editing big documents!) if self._cache.line_indexes is None: # Create list of line lengths. line_lengths = map(len, self.lines) # Calculate cumulative sums. indexes = [0] append = indexes.append pos = 0 for line_length in line_lengths: pos += line_length + 1 append(pos) # Remove the last item. (This is not a new line.) if len(indexes) > 1: indexes.pop() self._cache.line_indexes = indexes return self._cache.line_indexes @property def lines_from_current(self): """ Array of the lines starting from the current line, until the last line. """ return self.lines[self.cursor_position_row:] @property def line_count(self): r""" Return the number of lines in this document. If the document ends with a trailing \n, that counts as the beginning of a new line. """ return len(self.lines) @property def current_line(self): """ Return the text on the line where the cursor is. (when the input consists of just one line, it equals `text`. """ return self.current_line_before_cursor + self.current_line_after_cursor @property def leading_whitespace_in_current_line(self): """ The leading whitespace in the left margin of the current line. """ current_line = self.current_line length = len(current_line) - len(current_line.lstrip()) return current_line[:length] def _get_char_relative_to_cursor(self, offset=0): """ Return character relative to cursor position, or empty string """ try: return self.text[self.cursor_position + offset] except IndexError: return '' @property def on_first_line(self): """ True when we are at the first line. """ return self.cursor_position_row == 0 @property def on_last_line(self): """ True when we are at the last line. """ return self.cursor_position_row == self.line_count - 1 @property def cursor_position_row(self): """ Current row. (0-based.) """ row, _ = self._find_line_start_index(self.cursor_position) return row @property def cursor_position_col(self): """ Current column. (0-based.) """ # (Don't use self.text_before_cursor to calculate this. Creating # substrings and doing rsplit is too expensive for getting the cursor # position.) _, line_start_index = self._find_line_start_index(self.cursor_position) return self.cursor_position - line_start_index def _find_line_start_index(self, index): """ For the index of a character at a certain line, calculate the index of the first character on that line. Return (row, index) tuple. """ indexes = self._line_start_indexes pos = bisect.bisect_right(indexes, index) - 1 return pos, indexes[pos] def translate_index_to_position(self, index): """ Given an index for the text, return the corresponding (row, col) tuple. (0-based. Returns (0, 0) for index=0.) """ # Find start of this line. row, row_index = self._find_line_start_index(index) col = index - row_index return row, col def translate_row_col_to_index(self, row, col): """ Given a (row, col) tuple, return the corresponding index. (Row and col params are 0-based.) Negative row/col values are turned into zero. """ try: result = self._line_start_indexes[row] line = self.lines[row] except IndexError: if row < 0: result = self._line_start_indexes[0] line = self.lines[0] else: result = self._line_start_indexes[-1] line = self.lines[-1] result += max(0, min(col, len(line))) # Keep in range. (len(self.text) is included, because the cursor can be # right after the end of the text as well.) result = max(0, min(result, len(self.text))) return result @property def is_cursor_at_the_end(self): """ True when the cursor is at the end of the text. """ return self.cursor_position == len(self.text) @property def is_cursor_at_the_end_of_line(self): """ True when the cursor is at the end of this line. """ return self.current_char in ('\n', '') def has_match_at_current_position(self, sub): """ `True` when this substring is found at the cursor position. """ return self.text.find(sub, self.cursor_position) == self.cursor_position def find(self, sub, in_current_line=False, include_current_position=False, ignore_case=False, count=1): """ Find `text` after the cursor, return position relative to the cursor position. Return `None` if nothing was found. :param count: Find the n-th occurrence. """ assert isinstance(ignore_case, bool) if in_current_line: text = self.current_line_after_cursor else: text = self.text_after_cursor if not include_current_position: if len(text) == 0: return # (Otherwise, we always get a match for the empty string.) else: text = text[1:] flags = re.IGNORECASE if ignore_case else 0 iterator = re.finditer(re.escape(sub), text, flags) try: for i, match in enumerate(iterator): if i + 1 == count: if include_current_position: return match.start(0) else: return match.start(0) + 1 except StopIteration: pass def find_all(self, sub, ignore_case=False): """ Find all occurrences of the substring. Return a list of absolute positions in the document. """ flags = re.IGNORECASE if ignore_case else 0 return [a.start() for a in re.finditer(re.escape(sub), self.text, flags)] def find_backwards(self, sub, in_current_line=False, ignore_case=False, count=1): """ Find `text` before the cursor, return position relative to the cursor position. Return `None` if nothing was found. :param count: Find the n-th occurrence. """ if in_current_line: before_cursor = self.current_line_before_cursor[::-1] else: before_cursor = self.text_before_cursor[::-1] flags = re.IGNORECASE if ignore_case else 0 iterator = re.finditer(re.escape(sub[::-1]), before_cursor, flags) try: for i, match in enumerate(iterator): if i + 1 == count: return - match.start(0) - len(sub) except StopIteration: pass def get_word_before_cursor(self, WORD=False, pattern=None): """ Give the word before the cursor. If we have whitespace before the cursor this returns an empty string. :param pattern: (None or compiled regex). When given, use this regex pattern. """ if self._is_word_before_cursor_complete(WORD=WORD, pattern=pattern): # Space before the cursor or no text before cursor. return '' text_before_cursor = self.text_before_cursor start = self.find_start_of_previous_word(WORD=WORD, pattern=pattern) return text_before_cursor[len(text_before_cursor) + start:] def _is_word_before_cursor_complete(self, WORD=False, pattern=None): if pattern: return self.find_start_of_previous_word(pattern=pattern) is None else: return self.text_before_cursor == '' or self.text_before_cursor[-1:].isspace() def find_start_of_previous_word(self, count=1, WORD=False, pattern=None): """ Return an index relative to the cursor position pointing to the start of the previous word. Return `None` if nothing was found. :param pattern: (None or compiled regex). When given, use this regex pattern. """ assert not (WORD and pattern) # Reverse the text before the cursor, in order to do an efficient # backwards search. text_before_cursor = self.text_before_cursor[::-1] if pattern: regex = pattern elif WORD: regex = _FIND_BIG_WORD_RE else: regex = _FIND_WORD_RE iterator = regex.finditer(text_before_cursor) try: for i, match in enumerate(iterator): if i + 1 == count: return - match.end(0) except StopIteration: pass def find_boundaries_of_current_word(self, WORD=False, include_leading_whitespace=False, include_trailing_whitespace=False): """ Return the relative boundaries (startpos, endpos) of the current word under the cursor. (This is at the current line, because line boundaries obviously don't belong to any word.) If not on a word, this returns (0,0) """ text_before_cursor = self.current_line_before_cursor[::-1] text_after_cursor = self.current_line_after_cursor def get_regex(include_whitespace): return { (False, False): _FIND_CURRENT_WORD_RE, (False, True): _FIND_CURRENT_WORD_INCLUDE_TRAILING_WHITESPACE_RE, (True, False): _FIND_CURRENT_BIG_WORD_RE, (True, True): _FIND_CURRENT_BIG_WORD_INCLUDE_TRAILING_WHITESPACE_RE, }[(WORD, include_whitespace)] match_before = get_regex(include_leading_whitespace).search(text_before_cursor) match_after = get_regex(include_trailing_whitespace).search(text_after_cursor) # When there is a match before and after, and we're not looking for # WORDs, make sure that both the part before and after the cursor are # either in the [a-zA-Z_] alphabet or not. Otherwise, drop the part # before the cursor. if not WORD and match_before and match_after: c1 = self.text[self.cursor_position - 1] c2 = self.text[self.cursor_position] alphabet = string.ascii_letters + '0123456789_' if (c1 in alphabet) != (c2 in alphabet): match_before = None return ( - match_before.end(1) if match_before else 0, match_after.end(1) if match_after else 0 ) def get_word_under_cursor(self, WORD=False): """ Return the word, currently below the cursor. This returns an empty string when the cursor is on a whitespace region. """ start, end = self.find_boundaries_of_current_word(WORD=WORD) return self.text[self.cursor_position + start: self.cursor_position + end] def find_next_word_beginning(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the start of the next word. Return `None` if nothing was found. """ if count < 0: return self.find_previous_word_beginning(count=-count, WORD=WORD) regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterator = regex.finditer(self.text_after_cursor) try: for i, match in enumerate(iterator): # Take first match, unless it's the word on which we're right now. if i == 0 and match.start(1) == 0: count += 1 if i + 1 == count: return match.start(1) except StopIteration: pass def find_next_word_ending(self, include_current_position=False, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the end of the next word. Return `None` if nothing was found. """ if count < 0: return self.find_previous_word_ending(count=-count, WORD=WORD) if include_current_position: text = self.text_after_cursor else: text = self.text_after_cursor[1:] regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterable = regex.finditer(text) try: for i, match in enumerate(iterable): if i + 1 == count: value = match.end(1) if include_current_position: return value else: return value + 1 except StopIteration: pass def find_previous_word_beginning(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the start of the previous word. Return `None` if nothing was found. """ if count < 0: return self.find_next_word_beginning(count=-count, WORD=WORD) regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterator = regex.finditer(self.text_before_cursor[::-1]) try: for i, match in enumerate(iterator): if i + 1 == count: return - match.end(1) except StopIteration: pass def find_previous_word_ending(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the end of the previous word. Return `None` if nothing was found. """ if count < 0: return self.find_next_word_ending(count=-count, WORD=WORD) text_before_cursor = self.text_after_cursor[:1] + self.text_before_cursor[::-1] regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE iterator = regex.finditer(text_before_cursor) try: for i, match in enumerate(iterator): # Take first match, unless it's the word on which we're right now. if i == 0 and match.start(1) == 0: count += 1 if i + 1 == count: return -match.start(1) + 1 except StopIteration: pass def find_next_matching_line(self, match_func, count=1): """ Look downwards for empty lines. Return the line index, relative to the current line. """ result = None for index, line in enumerate(self.lines[self.cursor_position_row + 1:]): if match_func(line): result = 1 + index count -= 1 if count == 0: break return result def find_previous_matching_line(self, match_func, count=1): """ Look upwards for empty lines. Return the line index, relative to the current line. """ result = None for index, line in enumerate(self.lines[:self.cursor_position_row][::-1]): if match_func(line): result = -1 - index count -= 1 if count == 0: break return result def get_cursor_left_position(self, count=1): """ Relative position for cursor left. """ if count < 0: return self.get_cursor_right_position(-count) return - min(self.cursor_position_col, count) def get_cursor_right_position(self, count=1): """ Relative position for cursor_right. """ if count < 0: return self.get_cursor_left_position(-count) return min(count, len(self.current_line_after_cursor)) def get_cursor_up_position(self, count=1, preferred_column=None): """ Return the relative cursor position (character index) where we would be if the user pressed the arrow-up button. :param preferred_column: When given, go to this column instead of staying at the current column. """ assert count >= 1 column = self.cursor_position_col if preferred_column is None else preferred_column return self.translate_row_col_to_index( max(0, self.cursor_position_row - count), column) - self.cursor_position def get_cursor_down_position(self, count=1, preferred_column=None): """ Return the relative cursor position (character index) where we would be if the user pressed the arrow-down button. :param preferred_column: When given, go to this column instead of staying at the current column. """ assert count >= 1 column = self.cursor_position_col if preferred_column is None else preferred_column return self.translate_row_col_to_index( self.cursor_position_row + count, column) - self.cursor_position def find_enclosing_bracket_right(self, left_ch, right_ch, end_pos=None): """ Find the right bracket enclosing current position. Return the relative position to the cursor position. When `end_pos` is given, don't look past the position. """ if self.current_char == right_ch: return 0 if end_pos is None: end_pos = len(self.text) else: end_pos = min(len(self.text), end_pos) stack = 1 # Look forward. for i in range(self.cursor_position + 1, end_pos): c = self.text[i] if c == left_ch: stack += 1 elif c == right_ch: stack -= 1 if stack == 0: return i - self.cursor_position def find_enclosing_bracket_left(self, left_ch, right_ch, start_pos=None): """ Find the left bracket enclosing current position. Return the relative position to the cursor position. When `start_pos` is given, don't look past the position. """ if self.current_char == left_ch: return 0 if start_pos is None: start_pos = 0 else: start_pos = max(0, start_pos) stack = 1 # Look backward. for i in range(self.cursor_position - 1, start_pos - 1, -1): c = self.text[i] if c == right_ch: stack += 1 elif c == left_ch: stack -= 1 if stack == 0: return i - self.cursor_position def find_matching_bracket_position(self, start_pos=None, end_pos=None): """ Return relative cursor position of matching [, (, { or < bracket. When `start_pos` or `end_pos` are given. Don't look past the positions. """ # Look for a match. for A, B in '()', '[]', '{}', '<>': if self.current_char == A: return self.find_enclosing_bracket_right(A, B, end_pos=end_pos) or 0 elif self.current_char == B: return self.find_enclosing_bracket_left(A, B, start_pos=start_pos) or 0 return 0 def get_start_of_document_position(self): """ Relative position for the start of the document. """ return - self.cursor_position def get_end_of_document_position(self): """ Relative position for the end of the document. """ return len(self.text) - self.cursor_position def get_start_of_line_position(self, after_whitespace=False): """ Relative position for the start of this line. """ if after_whitespace: current_line = self.current_line return len(current_line) - len(current_line.lstrip()) - self.cursor_position_col else: return - len(self.current_line_before_cursor) def get_end_of_line_position(self): """ Relative position for the end of this line. """ return len(self.current_line_after_cursor) def last_non_blank_of_current_line_position(self): """ Relative position for the last non blank character of this line. """ return len(self.current_line.rstrip()) - self.cursor_position_col - 1 def get_column_cursor_position(self, column): """ Return the relative cursor position for this column at the current line. (It will stay between the boundaries of the line in case of a larger number.) """ line_length = len(self.current_line) current_column = self.cursor_position_col column = max(0, min(line_length, column)) return column - current_column def selection_range(self): # XXX: shouldn't this return `None` if there is no selection??? """ Return (from, to) tuple of the selection. start and end position are included. This doesn't take the selection type into account. Use `selection_ranges` instead. """ if self.selection: from_, to = sorted([self.cursor_position, self.selection.original_cursor_position]) else: from_, to = self.cursor_position, self.cursor_position return from_, to def selection_ranges(self): """ Return a list of `(from, to)` tuples for the selection or none if nothing was selected. The upper boundary is not included. This will yield several (from, to) tuples in case of a BLOCK selection. This will return zero ranges, like (8,8) for empty lines in a block selection. """ if self.selection: from_, to = sorted([self.cursor_position, self.selection.original_cursor_position]) if self.selection.type == SelectionType.BLOCK: from_line, from_column = self.translate_index_to_position(from_) to_line, to_column = self.translate_index_to_position(to) from_column, to_column = sorted([from_column, to_column]) lines = self.lines if vi_mode(): to_column += 1 for l in range(from_line, to_line + 1): line_length = len(lines[l]) if from_column <= line_length: yield (self.translate_row_col_to_index(l, from_column), self.translate_row_col_to_index(l, min(line_length, to_column))) else: # In case of a LINES selection, go to the start/end of the lines. if self.selection.type == SelectionType.LINES: from_ = max(0, self.text.rfind('\n', 0, from_) + 1) if self.text.find('\n', to) >= 0: to = self.text.find('\n', to) else: to = len(self.text) - 1 # In Vi mode, the upper boundary is always included. For Emacs, # that's not the case. if vi_mode(): to += 1 yield from_, to def selection_range_at_line(self, row): """ If the selection spans a portion of the given line, return a (from, to) tuple. The returned upper boundary is not included in the selection, so `(0, 0)` is an empty selection. `(0, 1)`, is a one character selection. Returns None if the selection doesn't cover this line at all. """ if self.selection: line = self.lines[row] row_start = self.translate_row_col_to_index(row, 0) row_end = self.translate_row_col_to_index(row, len(line)) from_, to = sorted([self.cursor_position, self.selection.original_cursor_position]) # Take the intersection of the current line and the selection. intersection_start = max(row_start, from_) intersection_end = min(row_end, to) if intersection_start <= intersection_end: if self.selection.type == SelectionType.LINES: intersection_start = row_start intersection_end = row_end elif self.selection.type == SelectionType.BLOCK: _, col1 = self.translate_index_to_position(from_) _, col2 = self.translate_index_to_position(to) col1, col2 = sorted([col1, col2]) if col1 > len(line): return # Block selection doesn't cross this line. intersection_start = self.translate_row_col_to_index(row, col1) intersection_end = self.translate_row_col_to_index(row, col2) _, from_column = self.translate_index_to_position(intersection_start) _, to_column = self.translate_index_to_position(intersection_end) # In Vi mode, the upper boundary is always included. For Emacs # mode, that's not the case. if vi_mode(): to_column += 1 return from_column, to_column def cut_selection(self): """ Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the document represents the new document when the selection is cut, and the clipboard data, represents whatever has to be put on the clipboard. """ if self.selection: cut_parts = [] remaining_parts = [] new_cursor_position = self.cursor_position last_to = 0 for from_, to in self.selection_ranges(): if last_to == 0: new_cursor_position = from_ remaining_parts.append(self.text[last_to:from_]) cut_parts.append(self.text[from_:to]) last_to = to remaining_parts.append(self.text[last_to:]) cut_text = '\n'.join(cut_parts) remaining_text = ''.join(remaining_parts) # In case of a LINES selection, don't include the trailing newline. if self.selection.type == SelectionType.LINES and cut_text.endswith('\n'): cut_text = cut_text[:-1] return (Document(text=remaining_text, cursor_position=new_cursor_position), ClipboardData(cut_text, self.selection.type)) else: return self, ClipboardData('') def paste_clipboard_data(self, data, paste_mode=PasteMode.EMACS, count=1): """ Return a new :class:`.Document` instance which contains the result if we would paste this data at the current cursor position. :param paste_mode: Where to paste. (Before/after/emacs.) :param count: When >1, Paste multiple times. """ assert isinstance(data, ClipboardData) assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) before = (paste_mode == PasteMode.VI_BEFORE) after = (paste_mode == PasteMode.VI_AFTER) if data.type == SelectionType.CHARACTERS: if after: new_text = (self.text[:self.cursor_position + 1] + data.text * count + self.text[self.cursor_position + 1:]) else: new_text = self.text_before_cursor + data.text * count + self.text_after_cursor new_cursor_position = self.cursor_position + len(data.text) * count if before: new_cursor_position -= 1 elif data.type == SelectionType.LINES: l = self.cursor_position_row if before: lines = self.lines[:l] + [data.text] * count + self.lines[l:] new_text = '\n'.join(lines) new_cursor_position = len(''.join(self.lines[:l])) + l else: lines = self.lines[:l + 1] + [data.text] * count + self.lines[l + 1:] new_cursor_position = len(''.join(self.lines[:l + 1])) + l + 1 new_text = '\n'.join(lines) elif data.type == SelectionType.BLOCK: lines = self.lines[:] start_line = self.cursor_position_row start_column = self.cursor_position_col + (0 if before else 1) for i, line in enumerate(data.text.split('\n')): index = i + start_line if index >= len(lines): lines.append('') lines[index] = lines[index].ljust(start_column) lines[index] = lines[index][:start_column] + line * count + lines[index][start_column:] new_text = '\n'.join(lines) new_cursor_position = self.cursor_position + (0 if before else 1) return Document(text=new_text, cursor_position=new_cursor_position) def empty_line_count_at_the_end(self): """ Return number of empty lines at the end of the document. """ count = 0 for line in self.lines[::-1]: if not line or line.isspace(): count += 1 else: break return count def start_of_paragraph(self, count=1, before=False): """ Return the start of the current paragraph. (Relative cursor position.) """ def match_func(text): return not text or text.isspace() line_index = self.find_previous_matching_line(match_func=match_func, count=count) if line_index: add = 0 if before else 1 return min(0, self.get_cursor_up_position(count=-line_index) + add) else: return -self.cursor_position def end_of_paragraph(self, count=1, after=False): """ Return the end of the current paragraph. (Relative cursor position.) """ def match_func(text): return not text or text.isspace() line_index = self.find_next_matching_line(match_func=match_func, count=count) if line_index: add = 0 if after else 1 return max(0, self.get_cursor_down_position(count=line_index) - add) else: return len(self.text_after_cursor) # Modifiers. def insert_after(self, text): """ Create a new document, with this text inserted after the buffer. It keeps selection ranges and cursor position in sync. """ return Document( text=self.text + text, cursor_position=self.cursor_position, selection=self.selection) def insert_before(self, text): """ Create a new document, with this text inserted before the buffer. It keeps selection ranges and cursor position in sync. """ selection_state = self.selection if selection_state: selection_state = SelectionState( original_cursor_position=selection_state.original_cursor_position + len(text), type=selection_state.type) return Document( text=text + self.text, cursor_position=self.cursor_position + len(text), selection=selection_state) prompt_toolkit-2.0.10/prompt_toolkit/enums.py0000644000175100017510000000052613545407204023106 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals class EditingMode(object): # The set of key bindings that is active. VI = 'VI' EMACS = 'EMACS' #: Name of the search buffer. SEARCH_BUFFER = 'SEARCH_BUFFER' #: Name of the default buffer. DEFAULT_BUFFER = 'DEFAULT_BUFFER' #: Name of the system buffer. SYSTEM_BUFFER = 'SYSTEM_BUFFER' prompt_toolkit-2.0.10/prompt_toolkit/eventloop/0000755000175100017510000000000013545410361023413 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/eventloop/__init__.py0000644000175100017510000000214113545407204025524 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .async_generator import ( AsyncGeneratorItem, consume_async_generator, generator_to_async_generator, ) from .base import EventLoop, get_traceback_from_context from .coroutine import From, Return, ensure_future from .defaults import ( call_from_executor, create_asyncio_event_loop, create_event_loop, get_event_loop, run_in_executor, run_until_complete, set_event_loop, use_asyncio_event_loop, ) from .event import Event from .future import Future, InvalidStateError __all__ = [ # Base. 'EventLoop', 'get_traceback_from_context', # Coroutine. 'From', 'Return', 'ensure_future', # Async generators 'AsyncGeneratorItem', 'generator_to_async_generator', 'consume_async_generator', # Defaults 'create_event_loop', 'create_asyncio_event_loop', 'use_asyncio_event_loop', 'get_event_loop', 'set_event_loop', 'run_in_executor', 'call_from_executor', 'run_until_complete', # Futures. 'Future', 'InvalidStateError', # Event. 'Event', ] prompt_toolkit-2.0.10/prompt_toolkit/eventloop/async_generator.py0000644000175100017510000000742013545407204027155 0ustar jonathanjonathan00000000000000""" Implementation for async generators. An asynchronous generator is one that can both produce `Future` objects as well as actual values. These values have to be wrapped in a `AsyncGeneratorItem` in order to be recognized. In the future, we can use the asynchronous generators from Python 3 (and asyncio). .. code:: python def async_generator(): yield From(...) yield AsyncGeneratorItem(some_value) yield From(...) yield AsyncGeneratorItem(some_value) ... """ from __future__ import unicode_literals from threading import RLock from six.moves.queue import Queue from .coroutine import From, Return from .defaults import run_in_executor from .future import Future __all__ = [ 'AsyncGeneratorItem', 'generator_to_async_generator', 'consume_async_generator', ] class AsyncGeneratorItem(object): def __init__(self, value): self.value = value def __repr__(self): return 'AsyncGeneratorItem(%r)' % (self.value, ) def generator_to_async_generator(get_iterable): """ Turn a generator or iterable into an async generator. This works by running the generator in a background thread. The new async generator will yield both `Future` objects as well as the original items. :param get_iterable: Function that returns a generator or iterable when called. """ q = Queue() f = Future() l = RLock() quitting = False def runner(): """ Consume the generator in background thread. When items are received, they'll be pushed to the queue and the Future is set. """ for item in get_iterable(): with l: q.put(item) if not f.done(): f.set_result(None) # When this async generator was cancelled (closed), stop this # thread. if quitting: break with l: if not f.done(): f.set_result(None) # Start background thread. done_f = run_in_executor(runner, _daemon=True) try: while not done_f.done(): # Wait for next item(s): yield Future. yield From(f) # Items received. Yield all items so far. with l: while not q.empty(): yield AsyncGeneratorItem(q.get()) f = Future() # Yield final items. while not q.empty(): yield q.get() finally: # When this async generator is closed (GeneratorExit exception, stop # the background thread as well. - we don't need that anymore.) quitting = True def consume_async_generator(iterator, cancel, item_callback): """ Consume an asynchronous generator. :param cancel: Cancel the consumption of the generator when this callable return True. :param item_callback: This will be called for each item that we receive. """ assert callable(cancel) assert callable(item_callback) send = None try: item = iterator.send(send) except StopIteration: return while True: if cancel(): break if isinstance(item, AsyncGeneratorItem): # Got item. item_callback(item.value) send = None elif isinstance(item, Future): # Process future. try: send = yield From(item) except BaseException as e: try: item = iterator.throw(e) except StopIteration: break else: raise TypeError('Expecting Completion or Future, got %r' % (item, )) try: item = iterator.send(send) except StopIteration: break raise Return(None) prompt_toolkit-2.0.10/prompt_toolkit/eventloop/asyncio_posix.py0000644000175100017510000000527613545407204026670 0ustar jonathanjonathan00000000000000""" Posix asyncio event loop. """ from __future__ import unicode_literals import asyncio from .base import EventLoop from .context import wrap_in_current_context from .future import Future from .utils import ThreadWithFuture __all__ = [ 'PosixAsyncioEventLoop', ] class PosixAsyncioEventLoop(EventLoop): """ Wrapper around the Asyncio event loop, but compatible with prompt_toolkit. """ def __init__(self, loop=None): super(PosixAsyncioEventLoop, self).__init__() self.loop = loop or asyncio.get_event_loop() self.closed = False def close(self): # Note: we should not close the asyncio loop itself, because that one # was not created here. self.closed = True def run_until_complete(self, future, inputhook=None): if inputhook: raise ValueError("PosixAsyncioEventLoop doesn't support input hooks.") return self.loop.run_until_complete(future) def run_forever(self, inputhook=None): if inputhook: raise ValueError("PosixAsyncioEventLoop doesn't support input hooks.") self.loop.run_forever() def run_in_executor(self, callback, _daemon=False): if _daemon: # Asyncio doesn't support 'daemon' executors. th = ThreadWithFuture(callback, daemon=True) self.call_from_executor(th.start) return th.future else: callback = wrap_in_current_context(callback) asyncio_f = self.loop.run_in_executor(None, callback) return Future.from_asyncio_future(asyncio_f, loop=self) def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. """ callback = wrap_in_current_context(callback) self.loop.call_soon_threadsafe(callback) def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " callback = wrap_in_current_context(callback) self.loop.add_reader(fd, callback) def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " self.loop.remove_reader(fd) def add_signal_handler(self, signum, handler): return self.loop.add_signal_handler(signum, handler) def get_exception_handler(self): return self.loop.get_exception_handler() def set_exception_handler(self, handler): self.loop.set_exception_handler(handler) def call_exception_handler(self, context): self.loop.call_exception_handler(context) def default_exception_handler(self, context): self.loop.default_exception_handler(context) prompt_toolkit-2.0.10/prompt_toolkit/eventloop/asyncio_win32.py0000644000175100017510000000737213545407204026467 0ustar jonathanjonathan00000000000000""" Win32 asyncio event loop. Windows notes: - Somehow it doesn't seem to work with the 'ProactorEventLoop'. """ from __future__ import unicode_literals import asyncio from .base import EventLoop from .context import wrap_in_current_context from .future import Future from .utils import ThreadWithFuture from .win32 import wait_for_handles __all__ = [ 'Win32AsyncioEventLoop', ] class Win32AsyncioEventLoop(EventLoop): """ Wrapper around the Asyncio event loop, but compatible with prompt_toolkit. """ def __init__(self, loop=None): super(Win32AsyncioEventLoop, self).__init__() self.loop = loop or asyncio.get_event_loop() self.closed = False # Maps win32 handles to their callbacks. self._handle_callbacks = {} # HANDLE fd to callback. def close(self): # Note: we should not close the asyncio loop itself, because that one # was not created here. self.closed = True def run_until_complete(self, future, inputhook=None): if inputhook: raise ValueError("Win32AsyncioEventLoop doesn't support input hooks.") return self.loop.run_until_complete(future) def run_forever(self, inputhook=None): if inputhook: raise ValueError("Win32AsyncioEventLoop doesn't support input hooks.") self.loop.run_forever() def run_in_executor(self, callback, _daemon=False): if _daemon: # Asyncio doesn't support 'daemon' executors. th = ThreadWithFuture(callback, daemon=True) self.call_from_executor(th.start) return th.future else: asyncio_f = self.loop.run_in_executor(None, callback) return Future.from_asyncio_future(asyncio_f, loop=self) def call_from_executor(self, callback, _max_postpone_until=None): callback = wrap_in_current_context(callback) self.loop.call_soon_threadsafe(callback) def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " callback = wrap_in_current_context(callback) self.loop.add_reader(fd, callback) def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " self.loop.remove_reader(fd) def add_signal_handler(self, signum, handler): return self.loop.add_signal_handler(signum, handler) def get_exception_handler(self): return self.loop.get_exception_handler() def set_exception_handler(self, handler): self.loop.set_exception_handler(handler) def call_exception_handler(self, context): self.loop.call_exception_handler(context) def default_exception_handler(self, context): self.loop.default_exception_handler(context) def add_win32_handle(self, handle, callback): " Add a Win32 handle to the event loop. " callback = wrap_in_current_context(callback) self._handle_callbacks[handle.value] = callback # Add reader. def ready(): # Tell the callback that input's ready. try: callback() finally: self.run_in_executor(wait) # Wait for the input to become ready. # (Use an executor for this, the Windows asyncio event loop doesn't # allow us to wait for handles like stdin.) def wait(): if self._handle_callbacks.get(handle.value) != callback: return wait_for_handles([handle]) self.call_from_executor(ready) self.run_in_executor(wait, _daemon=True) def remove_win32_handle(self, handle): " Remove a Win32 handle from the event loop. " if handle.value in self._handle_callbacks: del self._handle_callbacks[handle.value] prompt_toolkit-2.0.10/prompt_toolkit/eventloop/base.py0000644000175100017510000001315413545407204024705 0ustar jonathanjonathan00000000000000""" Base event loop interface. The naming convention is kept similar to asyncio as much as possible. A special thanks to asyncio (tulip), Twisted, Tornado and Trollius for setting a good example on how to implement event loops. Possible, in the future, we'll run entirely on top of asyncio, but right now, we're still supporting Python 2. """ from __future__ import unicode_literals import sys from abc import ABCMeta, abstractmethod from six import with_metaclass from prompt_toolkit.log import logger __all__ = [ 'EventLoop', 'get_traceback_from_context', ] class EventLoop(with_metaclass(ABCMeta, object)): """ Eventloop interface. """ def __init__(self): self._exception_handler = None @abstractmethod def run_until_complete(self, future, inputhook=None): """ Keep running until this future has been set. Return the Future's result, or raise its exception. """ def run_forever(self, inputhook=None): """ Run loop forever. """ f = self.create_future() self.run_until_complete(f, inputhook=inputhook) @abstractmethod def close(self): """ Clean up of resources. Eventloop cannot be reused a second time after this call. """ @abstractmethod def add_reader(self, fd, callback): """ Start watching the file descriptor for read availability and then call the callback. """ @abstractmethod def remove_reader(self, fd): """ Stop watching the file descriptor for read availability. """ def add_win32_handle(self, handle, callback): """ Add a Windows Handle to the event loop. (Only applied to win32 loops.) """ raise NotImplementedError def remove_win32_handle(self, handle): """ Remove a Windows Handle from the event loop. (Only applied to win32 loops.) """ raise NotImplementedError @abstractmethod def run_in_executor(self, callback, _daemon=False): """ Run a long running function in a background thread. (This is recommended for code that could block the event loop.) Similar to Twisted's ``deferToThread``. """ @abstractmethod def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. :param _max_postpone_until: `None` or `time.time` value. For internal use. If the eventloop is saturated, consider this task to be low priority and postpone maximum until this timestamp. (For instance, repaint is done using low priority.) Note: In the past, this used to be a datetime.datetime instance, but apparently, executing `time.time` is more efficient: it does fewer system calls. (It doesn't read /etc/localtime.) """ def create_future(self): """ Create a `Future` object that is attached to this loop. This is the preferred way of creating futures. """ from .future import Future return Future(loop=self) def set_exception_handler(self, handler): """ Set the exception handler. """ assert handler is None or callable(handler) self._exception_handler = handler def get_exception_handler(self): """ Return the exception handler. """ return self._exception_handler def call_exception_handler(self, context): """ Call the current event loop exception handler. (Similar to ``asyncio.BaseEventLoop.call_exception_handler``.) """ if self._exception_handler: try: self._exception_handler(context) except Exception: logger.error('Exception in default exception handler', exc_info=True) else: try: self.default_exception_handler(context) except Exception: logger.error('Exception in default exception handler ' 'while handling an unexpected error ' 'in custom exception handler', exc_info=True) def default_exception_handler(self, context): """ Default exception handling. Thanks to asyncio for this function! """ message = context.get('message') if not message: message = 'Unhandled exception in event loop' exception = context.get('exception') if exception is not None: tb = get_traceback_from_context(context) exc_info = (type(exception), exception, tb) else: exc_info = False log_lines = [message] for key in sorted(context): if key in ('message', 'exception'): continue value = context[key] value = repr(value) log_lines.append('{}: {}'.format(key, value)) logger.error('\n'.join(log_lines), exc_info=exc_info) def get_traceback_from_context(context): """ Get the traceback object from the context. """ exception = context.get('exception') if exception: if hasattr(exception, '__traceback__'): return exception.__traceback__ else: # call_exception_handler() is usually called indirectly # from an except block. If it's not the case, the traceback # is undefined... return sys.exc_info()[2] return None prompt_toolkit-2.0.10/prompt_toolkit/eventloop/context.py0000644000175100017510000000477213545407204025465 0ustar jonathanjonathan00000000000000""" Task local storage for coroutines. Example:: # Create a new task local. my_local = TaskLocal() # Set/retrieve/erase: my_local.set(value) value = my_local.get() my_local.delete() A new scope can be created as follows:: with context(): ... Within this scope, a new value can be assigned, which is only visible within the scope. The scope as passed along when code is sent to an executor and back. """ from __future__ import unicode_literals from functools import wraps from threading import local __all__ = [ 'context', 'get_context_id', 'wrap_in_current_context', 'TaskLocal', 'TaskLocalNotSetError', ] _storage = local() _last_context_id = 0 def get_context_id(): " Return the current context ID or None. " try: return _storage.context_id except AttributeError: return 0 # Default context. class context(object): """ Context manager that activates a new scope. """ def __init__(self, context_id=None): global _last_context_id if context_id is not None: self.id = context_id else: _last_context_id += 1 self.id = _last_context_id def __enter__(self): try: self._previous_id = _storage.context_id except AttributeError: self._previous_id = None _storage.context_id = self.id return self.id def __exit__(self, *a): if self._previous_id is None: del _storage.context_id else: _storage.context_id = self._previous_id class TaskLocal(object): """ Like a thread local, but tied to the current task. """ def __init__(self): self._storage = {} def get(self): try: ctx = get_context_id() return self._storage[ctx] except KeyError: raise TaskLocalNotSetError def set(self, value): ctx = get_context_id() self._storage[ctx] = value def delete(self): ctx = get_context_id() try: del self._storage[ctx] except KeyError: pass def wrap_in_current_context(func): """ Decorator that takes a function, and ensures that when it's called, the current context will apply. """ assert callable(func) ctx_id = get_context_id() @wraps(func) def new_func(*a, **kw): with context(ctx_id): return func(*a, **kw) return new_func class TaskLocalNotSetError(Exception): pass prompt_toolkit-2.0.10/prompt_toolkit/eventloop/coroutine.py0000644000175100017510000000731113545407204026000 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import types from prompt_toolkit.eventloop.defaults import get_event_loop from prompt_toolkit.eventloop.future import Future __all__ = [ 'From', 'Return', 'ensure_future', ] def ensure_future(future_or_coroutine): """ Take a coroutine (generator) or a `Future` object, and make sure to return a `Future`. """ if isinstance(future_or_coroutine, Future): return future_or_coroutine elif isinstance(future_or_coroutine, types.GeneratorType): return _run_coroutine(future_or_coroutine) else: raise ValueError('Expecting coroutine or Future object. Got %r: %r' % ( type(future_or_coroutine), future_or_coroutine)) class Return(Exception): """ For backwards-compatibility with Python2: when "return" is not supported in a generator/coroutine. (Like Trollius.) Instead of ``return value``, in a coroutine do: ``raise Return(value)``. """ def __init__(self, value): self.value = value def __repr__(self): return 'Return(%r)' % (self.value, ) def From(obj): """ Used to emulate 'yield from'. (Like Trollius does.) """ return ensure_future(obj) def _run_coroutine(coroutine): """ Takes a generator that can yield Future instances. Example: def gen(): yield From(...) print('...') yield From(...) ensure_future(gen()) The values which are yielded by the given coroutine are supposed to be `Future` objects. """ assert isinstance(coroutine, types.GeneratorType) loop = get_event_loop() result_f = loop.create_future() # Wrap this future in a `_FutureRef`. We need this in order to be able to # break all its references when we're done. This is important # because in case of an exception, we want to be sure that # `result_f.__del__` is triggered as soon as possible, so that we see the # exception. # (If `step_next` had a direct reference to `result_f` and there is a # future that references `step_next`, then sometimes it won't be cleaned up # immediately. - I'm not sure how exactly, but in that case it requires the # garbage collector, because refcounting isn't sufficient.) ref = _FutureRef(result_f) # Loop through the generator. def step_next(f=None): " Execute next step of the coroutine." try: if f is None: new_f = coroutine.send(None) else: exc = f.exception() if exc: new_f = coroutine.throw(exc) else: new_f = coroutine.send(f.result()) except StopIteration: # Stop coroutine. Make sure that a result has been set in the future, # this will call the callbacks. (Also, don't take any result from # StopIteration, it has already been set using `raise Return()`. if not ref.future.done(): ref.future.set_result(None) ref.forget() except Return as e: ref.future.set_result(e.value) ref.forget() except BaseException as e: ref.future.set_exception(e) ref.forget() else: # Process yielded value from coroutine. assert isinstance(new_f, Future), 'got %r' % (new_f, ) @new_f.add_done_callback def continue_(_): step_next(new_f) # Start processing coroutine. step_next() return result_f class _FutureRef(object): def __init__(self, future): self.future = future def forget(self): " Forget reference. " self.future = None prompt_toolkit-2.0.10/prompt_toolkit/eventloop/defaults.py0000644000175100017510000000704513545407204025604 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import threading from prompt_toolkit.utils import is_windows from .base import EventLoop __all__ = [ 'create_event_loop', 'create_asyncio_event_loop', 'use_asyncio_event_loop', 'get_event_loop', 'set_event_loop', 'run_in_executor', 'call_from_executor', 'run_until_complete', ] def create_event_loop(recognize_win32_paste=True): """ Create and return an :class:`~prompt_toolkit.eventloop.base.EventLoop` instance. """ if is_windows(): from .win32 import Win32EventLoop return Win32EventLoop(recognize_paste=recognize_win32_paste) else: from .posix import PosixEventLoop return PosixEventLoop() def _get_asyncio_loop_cls(): # Inline import, to make sure the rest doesn't break on Python 2. (Where # asyncio is not available.) if is_windows(): from prompt_toolkit.eventloop.asyncio_win32 import Win32AsyncioEventLoop as AsyncioEventLoop else: from prompt_toolkit.eventloop.asyncio_posix import PosixAsyncioEventLoop as AsyncioEventLoop return AsyncioEventLoop def create_asyncio_event_loop(loop=None): """ Returns an asyncio :class:`~prompt_toolkit.eventloop.EventLoop` instance for usage in a :class:`~prompt_toolkit.application.Application`. It is a wrapper around an asyncio loop. :param loop: The asyncio eventloop (or `None` if the default asyncioloop should be used.) """ AsyncioEventLoop = _get_asyncio_loop_cls() return AsyncioEventLoop(loop) def use_asyncio_event_loop(loop=None): """ Use the asyncio event loop for prompt_toolkit applications. """ # Don't create a new loop if the current one uses asyncio already. current_loop = get_event_loop() if current_loop and isinstance(current_loop, _get_asyncio_loop_cls()): return set_event_loop(create_asyncio_event_loop(loop)) _loop = None _loop_lock = threading.RLock() def get_event_loop(): """ Return the current event loop. This will create a new loop if no loop was set yet. """ # When this function is called for the first time, and no loop has been # set: create one. global _loop with _loop_lock: # The following two lines are not atomic. I ended up in a situation # where two threads were calling `get_event_loop()` at the same time, # and that way we had two event loop instances. On one of the # instances, `call_from_executor` was called, but never executed # because that loop was not running. if _loop is None: _loop = create_event_loop() return _loop def set_event_loop(loop): """ Set the current event loop. :param loop: `EventLoop` instance or None. (Pass `None` to clear the current loop.) """ assert loop is None or isinstance(loop, EventLoop) global _loop _loop = loop def run_in_executor(callback, _daemon=False): """ Run a long running function in a background thread. """ return get_event_loop().run_in_executor(callback, _daemon=_daemon) def call_from_executor(callback, _max_postpone_until=None): """ Call this function in the main event loop. """ return get_event_loop().call_from_executor( callback, _max_postpone_until=_max_postpone_until) def run_until_complete(future, inputhook=None): """ Keep running until this future has been set. Return the Future's result, or raise its exception. """ return get_event_loop().run_until_complete(future, inputhook=inputhook) prompt_toolkit-2.0.10/prompt_toolkit/eventloop/event.py0000644000175100017510000000143013545407204025106 0ustar jonathanjonathan00000000000000""" Asyncronous event implementation. """ from __future__ import unicode_literals from .future import Future __all__ = [ 'Event' ] class Event(object): """ Like `asyncio.event`. The state is intially false. """ def __init__(self): self._state = False self._waiting_futures = [] def is_set(self): return self._state def clear(self): self._state = False def set(self): self._state = True futures = self._waiting_futures self._waiting_futures = [] for f in futures: f.set_result(None) def wait(self): if self._state: return Future.succeed(None) else: f = Future() self._waiting_futures.append(f) return f prompt_toolkit-2.0.10/prompt_toolkit/eventloop/future.py0000644000175100017510000001464213545407204025310 0ustar jonathanjonathan00000000000000""" Future implementation for the prompt_toolkit eventloop. """ from __future__ import print_function, unicode_literals import sys from .base import EventLoop from .context import context, get_context_id from .defaults import get_event_loop __all__ = [ 'Future', 'InvalidStateError', ] class InvalidStateError(Exception): " The operation is not allowed in this state. " class Future(object): """ `Future` object for use with the prompt_toolkit event loops. (Not by accident very similar to asyncio -- but much more limited in functionality. They are however not meant to be used interchangeable.) """ def __init__(self, loop=None): assert loop is None or isinstance(loop, EventLoop) self.loop = loop or get_event_loop() self.done_callbacks = [] self._result = None self._exception = None self._done = False self._retrieved_result = False # Keep track of which `TaskContext` was active when this Future was # created. This is the one that will be viewed as visible when the # callbacks are called. (This is required to make get_app/set_app work # together with coroutines, when there are multiple active # applications, attached to different outputs.) self._ctx_id = get_context_id() # Thanks to asyncio for the following destructor! # On Python 3.3 and older, objects with a destructor part of a reference # cycle are never destroyed. It's not more the case on Python 3.4 thanks # to the PEP 442. if sys.version_info >= (3, 4): def __del__(self): if self._exception and not self._retrieved_result: exc = self._exception context = { 'message': ('%s exception was never retrieved' % self.__class__.__name__), 'exception': exc, 'future': self, } self.loop.call_exception_handler(context) @classmethod def succeed(cls, result): """ Returns a Future for which the result has been set to the given result. Similar to Twisted's `Deferred.succeed()`. """ f = cls() f.set_result(result) return f @classmethod def fail(cls, result): """ Returns a Future for which the error has been set to the given result. Similar to Twisted's `Deferred.fail()`. """ f = cls() f.set_exception(result) return f def add_done_callback(self, callback): """ Add a callback to be run when the future becomes done. (This callback will be called with one argument only: this future object.) """ self.done_callbacks.append(callback) # When a result has already been set. Call callback right away. if self._done: def call_cb(): self._retrieved_result = True callback(self) self.loop.call_from_executor(call_cb) def set_result(self, result): " Mark the future done and set its result. " if self._done: raise InvalidStateError('Future result has been set already.') self._result = result self._done = True self._call_callbacks() def set_exception(self, exception): " Mark the future done and set an exception. " if self._done: raise InvalidStateError('Future result has been set already.') self._exception = exception self._done = True if self.done_callbacks: self._call_callbacks() else: # When an exception is set on a 'Future' object, but there # is no callback set to handle it, print the exception. # -- Uncomment for debugging. -- # import traceback, sys # print(''.join(traceback.format_stack()), file=sys.__stderr__) # print('Uncollected error: %r' % (exception, ), file=sys.__stderr__) pass def _call_callbacks(self): # Create a local copy of the callbacks. Otherwise, it could be that # another call to `add_done_callback` would add a new callback to this list # which would then be called twice. (Once from here, once from the # `add_done_callback` function directly. done_callbacks = self.done_callbacks[:] if done_callbacks: self._retrieved_result = True def call_them_all(): # Activate the original task context (and application) again. with context(self._ctx_id): # They should be called in order. for cb in done_callbacks: cb(self) self.loop.call_from_executor(call_them_all) def result(self): " Return the result this future represents. " if not self._done: raise InvalidStateError self._retrieved_result = True if self._exception: raise self._exception else: return self._result def exception(self): " Return the exception that was set on this future. " if not self._done: raise InvalidStateError self._retrieved_result = True return self._exception def done(self): """ Return True if the future is done. Done means either that a result / exception are available, or that the future was cancelled. """ return self._done def to_asyncio_future(self): """ Turn this `Future` into an asyncio `Future` object. """ from asyncio import Future asyncio_f = Future() @self.add_done_callback def _(f): if f.exception(): asyncio_f.set_exception(f.exception()) else: asyncio_f.set_result(f.result()) return asyncio_f @classmethod def from_asyncio_future(cls, asyncio_f, loop=None): """ Return a prompt_toolkit `Future` from the given asyncio Future. """ f = cls(loop=loop) @asyncio_f.add_done_callback def _(asyncio_f): if asyncio_f.exception(): f.set_exception(asyncio_f.exception()) else: f.set_result(asyncio_f.result()) return f def __iter__(self): " For compatibility with asyncio. " return self.to_asyncio_future().__iter__() __await__ = __iter__ prompt_toolkit-2.0.10/prompt_toolkit/eventloop/inputhook.py0000644000175100017510000000775713545407204026027 0ustar jonathanjonathan00000000000000""" Similar to `PyOS_InputHook` of the Python API. Some eventloops can have an inputhook to allow easy integration with other event loops. When the eventloop of prompt-toolkit is idle, it can call such a hook. This hook can call another eventloop that runs for a short while, for instance to keep a graphical user interface responsive. It's the responsibility of this hook to exit when there is input ready. There are two ways to detect when input is ready: - Call the `input_is_ready` method periodically. Quit when this returns `True`. - Add the `fileno` as a watch to the external eventloop. Quit when file descriptor becomes readable. (But don't read from it.) Note that this is not the same as checking for `sys.stdin.fileno()`. The eventloop of prompt-toolkit allows thread-based executors, for example for asynchronous autocompletion. When the completion for instance is ready, we also want prompt-toolkit to gain control again in order to display that. An alternative to using input hooks, is to create a custom `EventLoop` class that controls everything. """ from __future__ import unicode_literals import os import threading from prompt_toolkit.utils import is_windows from .select import select_fds __all__ = [ 'InputHookContext', ] class InputHookContext(object): """ Given as a parameter to the inputhook. """ def __init__(self): self._input_is_ready = None self._r, self._w = os.pipe() def input_is_ready(self): """ Return True when the input is ready. """ return self._input_is_ready(wait=False) def fileno(self): """ File descriptor that will become ready when the event loop needs to go on. """ return self._r def call_inputhook(self, input_is_ready_func, inputhook): """ Call the inputhook. (Called by a prompt-toolkit eventloop.) :param input_is_ready_func: A callable which returns True when there is input ready for the main event loop to process. This means that we should quit our input hook. This callable takes a boolean `wait`. Wen `True` this needs to be a blocking call which only returns when there is input ready. :param inputhook: This is a callable that runs the inputhook. This function should take this object (the `InputHookContext`) as input. """ assert callable(input_is_ready_func) assert callable(inputhook) self._input_is_ready = input_is_ready_func # Start thread that activates this pipe when there is input to process. def thread(): input_is_ready_func(wait=True) os.write(self._w, b'x') threading.Thread(target=thread).start() # Call inputhook. inputhook(self) # Flush the read end of the pipe. try: # Before calling 'os.read', call select.select. This is required # when the gevent monkey patch has been applied. 'os.read' is never # monkey patched and won't be cooperative, so that would block all # other select() calls otherwise. # See: http://www.gevent.org/gevent.os.html # Note: On Windows, this is apparently not an issue. # However, if we would ever want to add a select call, it # should use `windll.kernel32.WaitForMultipleObjects`, # because `select.select` can't wait for a pipe on Windows. if not is_windows(): select_fds([self._r], timeout=None) os.read(self._r, 1024) except OSError: # This happens when the window resizes and a SIGWINCH was received. # We get 'Error: [Errno 4] Interrupted system call' # Just ignore. pass self._input_is_ready = None def close(self): """ Clean up resources. """ if self._r: os.close(self._r) os.close(self._w) self._r = self._w = None prompt_toolkit-2.0.10/prompt_toolkit/eventloop/posix.py0000644000175100017510000002451113545407204025134 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import fcntl import os import signal import time from .base import EventLoop from .context import wrap_in_current_context from .future import Future from .inputhook import InputHookContext from .select import AutoSelector, Selector, fd_to_int from .utils import ThreadWithFuture __all__ = [ 'PosixEventLoop', ] _now = time.time class PosixEventLoop(EventLoop): """ Event loop for posix systems (Linux, Mac os X). """ def __init__(self, selector=AutoSelector): assert issubclass(selector, Selector) super(PosixEventLoop, self).__init__() self.closed = False self._running = False self._calls_from_executor = [] self._read_fds = {} # Maps fd to handler. self.selector = selector() self._signal_handler_mappings = {} # signal: previous_handler # Create a pipe for inter thread communication. self._schedule_pipe = os.pipe() fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK) self.selector.register(self._schedule_pipe[0]) # Create inputhook context. self._inputhook_context = None def run_until_complete(self, future, inputhook=None): """ Keep running the event loop until `future` has been set. :param future: :class:`prompt_toolkit.eventloop.future.Future` object. """ assert isinstance(future, Future) assert inputhook is None or callable(inputhook) if self._running: raise Exception('Event loop is already running') if self.closed: raise Exception('Event loop already closed.') try: self._running = True while not future.done(): self._run_once(inputhook) # Run one last time, to flush the pending `_calls_from_executor`s. if self._calls_from_executor: self._run_once(inputhook) finally: self._running = False def _run_once(self, inputhook): # Call inputhook. if inputhook: # Create input hook context. if self._inputhook_context is None: self._inputhook_context = InputHookContext() def ready(wait): " True when there is input ready. The inputhook should return control. " return self._ready_for_reading(None if wait else 0) != [] self._inputhook_context.call_inputhook(ready, inputhook) # Wait until input is ready. fds = self._ready_for_reading(None) # When any of the FDs are ready. Call the appropriate callback. if fds: # Create lists of high/low priority tasks. The main reason for this # is to allow painting the UI to happen as soon as possible, but # when there are many events happening, we don't want to call the # UI renderer 1000x per second. If the eventloop is completely # saturated with many CPU intensive tasks (like processing # input/output), we say that drawing the UI can be postponed a # little, to make CPU available. This will be a low priority task # in that case. tasks = [] low_priority_tasks = [] now = None # Lazy load time. (Fewer system calls.) for fd in fds: # For the 'call_from_executor' fd, put each pending # item on either the high or low priority queue. if fd == self._schedule_pipe[0]: # Flush all the pipe content. os.read(self._schedule_pipe[0], 1024) calls_from_executor = self._calls_from_executor[:] del self._calls_from_executor[:] for c, max_postpone_until in calls_from_executor: if max_postpone_until is None: # Execute now. tasks.append(c) else: # Execute soon, if `max_postpone_until` is in the future. now = now or _now() if max_postpone_until < now: tasks.append(c) else: low_priority_tasks.append((c, max_postpone_until)) else: handler = self._read_fds.get(fd) if handler: tasks.append(handler) # When there are high priority tasks, run all these. # Schedule low priority tasks for the next iteration. if tasks: for t in tasks: self._run_task(t) # Postpone low priority tasks. for t, max_postpone_until in low_priority_tasks: self.call_from_executor(t, _max_postpone_until=max_postpone_until) else: # Currently there are only low priority tasks -> run them right now. for t, _ in low_priority_tasks: self._run_task(t) def _run_task(self, t): """ Run a task in the event loop. If it fails, print the exception. By default, the event loop is not supposed to exit if one fd callback raises an exception. Doing so, would leave many coroutines in a not cleaned-up state. If this happens, this is a bug, and we have to print the stack. """ try: t() except BaseException as e: self.call_exception_handler({ 'exception': e }) def _ready_for_reading(self, timeout=None): """ Return the file descriptors that are ready for reading. """ fds = self.selector.select(timeout) return fds def add_signal_handler(self, signum, handler): """ Register a signal handler. Call `handler` when `signal` was received. The given handler will always be called in the same thread as the eventloop. (Like `call_from_executor`.) """ # Always process signals asynchronously, because these handlers can # write to the output, and doing this inside the signal handler causes # easily reentrant calls, giving runtime errors. # Further, this has to be thread safe. When the Application runs not in # the main thread, this function will still be called from the main # thread. (The only place where we can install signal handlers.) if handler is None: # Normally, `signal.signal` should never return `None`. For some # reason it happens here: # https://github.com/jonathanslenders/python-prompt-toolkit/pull/174 handler = 0 if handler in (signal.SIG_IGN, 0): # Clear handler. previous = signal.signal(signum, handler) self._signal_handler_mappings[signum] = handler else: # Set handler. def call_signal_handler(*a): self.call_from_executor(handler) previous = signal.signal(signum, call_signal_handler) self._signal_handler_mappings[signum] = handler # Return the previous signal handler. return self._signal_handler_mappings.get(signum, previous) def run_in_executor(self, callback, _daemon=False): """ Run a long running function in a background thread. (This is recommended for code that could block the event loop.) Similar to Twisted's ``deferToThread``. """ th = ThreadWithFuture(callback, daemon=_daemon) # Wait until the main thread is idle. # We start the thread by using `call_from_executor`. The event loop # favours processing input over `calls_from_executor`, so the thread # will not start until there is no more input to process and the main # thread becomes idle for an instant. This is good, because Python # threading favours CPU over I/O -- an autocompletion thread in the # background would cause a significantly slow down of the main thread. # It is mostly noticeable when pasting large portions of text while # having real time autocompletion while typing on. self.call_from_executor(th.start) return th.future def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. :param _max_postpone_until: `None` or `time.time` value. For internal use. If the eventloop is saturated, consider this task to be low priority and postpone maximum until this timestamp. (For instance, repaint is done using low priority.) """ assert _max_postpone_until is None or isinstance(_max_postpone_until, float) callback = wrap_in_current_context(callback) self._calls_from_executor.append((callback, _max_postpone_until)) if self._schedule_pipe: try: os.write(self._schedule_pipe[1], b'x') except (AttributeError, IndexError, OSError): # Handle race condition. We're in a different thread. # - `_schedule_pipe` could have become None in the meantime. # - We catch `OSError` (actually BrokenPipeError), because the # main thread could have closed the pipe already. pass def close(self): """ Close the event loop. The loop must not be running. """ assert not self._running self.closed = True # Close pipes. schedule_pipe = self._schedule_pipe self._schedule_pipe = None if schedule_pipe: os.close(schedule_pipe[0]) os.close(schedule_pipe[1]) if self._inputhook_context: self._inputhook_context.close() def add_reader(self, fd, callback): " Add read file descriptor to the event loop. " callback = wrap_in_current_context(callback) fd = fd_to_int(fd) self._read_fds[fd] = callback self.selector.register(fd) def remove_reader(self, fd): " Remove read file descriptor from the event loop. " fd = fd_to_int(fd) if fd in self._read_fds: del self._read_fds[fd] self.selector.unregister(fd) prompt_toolkit-2.0.10/prompt_toolkit/eventloop/select.py0000644000175100017510000001353513545407204025255 0ustar jonathanjonathan00000000000000""" Selectors for the Posix event loop. """ from __future__ import absolute_import, unicode_literals import abc import errno import select import sys import six __all__ = [ 'AutoSelector', 'PollSelector', 'SelectSelector', 'Selector', 'fd_to_int', ] def fd_to_int(fd): assert isinstance(fd, int) or hasattr(fd, 'fileno') if isinstance(fd, int): return fd else: return fd.fileno() class Selector(six.with_metaclass(abc.ABCMeta, object)): @abc.abstractmethod def register(self, fd): assert isinstance(fd, int) @abc.abstractmethod def unregister(self, fd): assert isinstance(fd, int) @abc.abstractmethod def select(self, timeout): pass @abc.abstractmethod def close(self): pass class AutoSelector(Selector): def __init__(self): self._fds = [] self._select_selector = SelectSelector() self._selectors = [self._select_selector] # When 'select.poll' exists, create a PollSelector. if hasattr(select, 'poll'): self._poll_selector = PollSelector() self._selectors.append(self._poll_selector) else: self._poll_selector = None # Use of the 'select' module, that was introduced in Python3.4. We don't # use it before 3.5 however, because this is the point where this module # retries interrupted system calls. if sys.version_info >= (3, 5): self._py3_selector = Python3Selector() self._selectors.append(self._py3_selector) else: self._py3_selector = None def register(self, fd): assert isinstance(fd, int) self._fds.append(fd) for sel in self._selectors: sel.register(fd) def unregister(self, fd): assert isinstance(fd, int) self._fds.remove(fd) for sel in self._selectors: sel.unregister(fd) def select(self, timeout): # Try Python 3 selector first. if self._py3_selector: try: return self._py3_selector.select(timeout) except PermissionError: # noqa (PermissionError doesn't exist in Py2) # We had a situation (in pypager) where epoll raised a # PermissionError when a local file descriptor was registered, # however poll and select worked fine. So, in that case, just # try using select below. pass try: # Prefer 'select.select', if we don't have much file descriptors. # This is more universal. return self._select_selector.select(timeout) except ValueError: # When we have more than 1024 open file descriptors, we'll always # get a "ValueError: filedescriptor out of range in select()" for # 'select'. In this case, try, using 'poll' instead. if self._poll_selector is not None: return self._poll_selector.select(timeout) else: raise def close(self): for sel in self._selectors: sel.close() class Python3Selector(Selector): """ Use of the Python3 'selectors' module. NOTE: Only use on Python 3.5 or newer! """ def __init__(self): assert sys.version_info >= (3, 5) import selectors # Inline import: Python3 only! self._sel = selectors.DefaultSelector() def register(self, fd): assert isinstance(fd, int) import selectors # Inline import: Python3 only! self._sel.register(fd, selectors.EVENT_READ, None) def unregister(self, fd): assert isinstance(fd, int) self._sel.unregister(fd) def select(self, timeout): events = self._sel.select(timeout=timeout) return [key.fileobj for key, mask in events] def close(self): self._sel.close() class PollSelector(Selector): def __init__(self): self._poll = select.poll() def register(self, fd): assert isinstance(fd, int) self._poll.register(fd, select.POLLIN) def unregister(self, fd): assert isinstance(fd, int) def select(self, timeout): tuples = self._poll.poll(timeout) # Returns (fd, event) tuples. return [t[0] for t in tuples] def close(self): pass # XXX class SelectSelector(Selector): """ Wrapper around select.select. When the SIGWINCH signal is handled, other system calls, like select are aborted in Python. This wrapper will retry the system call. """ def __init__(self): self._fds = [] def register(self, fd): self._fds.append(fd) def unregister(self, fd): self._fds.remove(fd) def select(self, timeout): while True: try: return select.select(self._fds, [], [], timeout)[0] except select.error as e: # Retry select call when EINTR if e.args and e.args[0] == errno.EINTR: continue else: raise def close(self): pass def select_fds(read_fds, timeout, selector=AutoSelector): """ Wait for a list of file descriptors (`read_fds`) to become ready for reading. This chooses the most appropriate select-tool for use in prompt-toolkit. """ # Map to ensure that we return the objects that were passed in originally. # Whether they are a fd integer or an object that has a fileno(). # (The 'poll' implementation for instance, returns always integers.) fd_map = dict((fd_to_int(fd), fd) for fd in read_fds) # Wait, using selector. sel = selector() try: for fd in read_fds: sel.register(fd) result = sel.select(timeout) if result is not None: return [fd_map[fd_to_int(fd)] for fd in result] finally: sel.close() prompt_toolkit-2.0.10/prompt_toolkit/eventloop/utils.py0000644000175100017510000000211113545407204025122 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import threading from .context import context, get_context_id from .future import Future __all__ = [ 'ThreadWithFuture', ] class ThreadWithFuture(object): """ Wrapper around `Thread`. :param daemon: If `True`, start as daemon. """ def __init__(self, target, daemon=False): self.target = target self.daemon = daemon self.future = Future() self._ctx_id = get_context_id() def start(self): """ Start the thread, `self.future` will be set when the thread is done. """ def run(): # Mark this context (and thus `Application`) active in the current # thread. with context(self._ctx_id): try: result = self.target() except BaseException as e: self.future.set_exception(e) else: self.future.set_result(result) t = threading.Thread(target=run) if self.daemon: t.daemon = True t.start() prompt_toolkit-2.0.10/prompt_toolkit/eventloop/win32.py0000644000175100017510000001514613545407204024740 0ustar jonathanjonathan00000000000000""" Win32 event loop. """ from __future__ import unicode_literals import msvcrt from ctypes import pointer, windll from ctypes.wintypes import BOOL, DWORD, HANDLE from ..win32_types import SECURITY_ATTRIBUTES from .base import EventLoop from .context import wrap_in_current_context from .future import Future from .inputhook import InputHookContext from .utils import ThreadWithFuture __all__ = [ 'Win32EventLoop', 'wait_for_handles', 'create_win32_event', ] WAIT_TIMEOUT = 0x00000102 INFINITE = -1 class Win32EventLoop(EventLoop): """ Event loop for Windows systems. :param recognize_paste: When True, try to discover paste actions and turn the event into a BracketedPaste. """ def __init__(self, recognize_paste=True): super(Win32EventLoop, self).__init__() self._event = create_win32_event() self._calls_from_executor = [] self.closed = False self._running = False # Additional readers. self._read_fds = {} # Maps HANDLE value to handler. # Create inputhook context. self._inputhook_context = None def run_until_complete(self, future, inputhook=None): """ Keep running the event loop until `future` has been set. :param future: :class:`prompt_toolkit.eventloop.future.Future` object. """ assert isinstance(future, Future) assert inputhook is None or callable(inputhook) if self._running: raise Exception('Event loop is already running') if self.closed: raise Exception('Event loop already closed.') try: self._running = True while not future.done(): self._run_once(inputhook) # Run one last time, to flush the pending `_calls_from_executor`s. if self._calls_from_executor: self._run_once(inputhook) finally: self._running = False def _run_once(self, inputhook): # Call inputhook. if inputhook: # Create input hook context. if self._inputhook_context is None: self._inputhook_context = InputHookContext() def ready(wait): " True when there is input ready. The inputhook should return control. " return bool(self._ready_for_reading(INFINITE if wait else 0)) self._inputhook_context.call_inputhook(ready, inputhook) # Wait for the next event. handle = self._ready_for_reading(INFINITE).value if handle == self._event.value: # When the Windows Event has been trigger, process the messages in the queue. windll.kernel32.ResetEvent(self._event) self._process_queued_calls_from_executor() elif handle in self._read_fds: callback = self._read_fds[handle] self._run_task(callback) def _run_task(self, t): try: t() except BaseException as e: self.call_exception_handler({ 'exception': e }) def _ready_for_reading(self, timeout=INFINITE): """ Return the handle that is ready for reading or `None` on timeout. """ handles = [self._event] handles.extend(HANDLE(fd) for fd in self._read_fds.keys()) return wait_for_handles(handles, timeout) def close(self): self.closed = True # Clean up Event object. windll.kernel32.CloseHandle(self._event) if self._inputhook_context: self._inputhook_context.close() def run_in_executor(self, callback, _daemon=False): """ Run a long running function in a background thread. (This is recommended for code that could block the event loop.) Similar to Twisted's ``deferToThread``. """ th = ThreadWithFuture(callback, daemon=_daemon) # Wait until the main thread is idle for an instant before starting the # executor. (Like in eventloop/posix.py, we start the executor using # `call_from_executor`.) self.call_from_executor(th.start) return th.future def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. """ callback = wrap_in_current_context(callback) # Append to list of pending callbacks. self._calls_from_executor.append(callback) # Set Windows event. windll.kernel32.SetEvent(self._event) def _process_queued_calls_from_executor(self): # Process calls from executor. calls_from_executor = self._calls_from_executor[:] del self._calls_from_executor[:] for c in calls_from_executor: self._run_task(c) def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " callback = wrap_in_current_context(callback) h = HANDLE(msvcrt.get_osfhandle(fd)) self.add_win32_handle(h, callback) def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " h = HANDLE(msvcrt.get_osfhandle(fd)) self.remove_win32_handle(h) def add_win32_handle(self, handle, callback): " Add a Win32 handle to the event loop. " callback = wrap_in_current_context(callback) self._read_fds[handle.value] = callback def remove_win32_handle(self, handle): " Remove a Win32 handle from the event loop. " if handle.value in self._read_fds: del self._read_fds[handle.value] def wait_for_handles(handles, timeout=INFINITE): """ Waits for multiple handles. (Similar to 'select') Returns the handle which is ready. Returns `None` on timeout. http://msdn.microsoft.com/en-us/library/windows/desktop/ms687025(v=vs.85).aspx """ assert isinstance(handles, list) # List of `HANDLE` objects. assert isinstance(timeout, int) arrtype = HANDLE * len(handles) handle_array = arrtype(*handles) ret = windll.kernel32.WaitForMultipleObjects( len(handle_array), handle_array, BOOL(False), DWORD(timeout)) if ret == WAIT_TIMEOUT: return None else: h = handle_array[ret] return HANDLE(h) def create_win32_event(): """ Creates a Win32 unnamed Event . http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx """ return HANDLE(windll.kernel32.CreateEventA( pointer(SECURITY_ATTRIBUTES()), BOOL(True), # Manual reset event. BOOL(False), # Initial state. None # Unnamed event object. )) prompt_toolkit-2.0.10/prompt_toolkit/filters/0000755000175100017510000000000013545410361023050 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/filters/__init__.py0000644000175100017510000000203713545407204025165 0ustar jonathanjonathan00000000000000""" Filters decide whether something is active or not (they decide about a boolean state). This is used to enable/disable features, like key bindings, parts of the layout and other stuff. For instance, we could have a `HasSearch` filter attached to some part of the layout, in order to show that part of the user interface only while the user is searching. Filters are made to avoid having to attach callbacks to all event in order to propagate state. However, they are lazy, they don't automatically propagate the state of what they are observing. Only when a filter is called (it's actually a callable), it will calculate its value. So, its not really reactive programming, but it's made to fit for this framework. Filters can be chained using ``&`` and ``|`` operations, and inverted using the ``~`` operator, for instance:: filter = has_focus('default') & ~ has_selection """ from __future__ import unicode_literals from .app import * from .base import Always, Condition, Filter, Never from .cli import * from .utils import is_true, to_filter prompt_toolkit-2.0.10/prompt_toolkit/filters/app.py0000644000175100017510000002072013545407204024205 0ustar jonathanjonathan00000000000000""" Filters that accept a `Application` as argument. """ from __future__ import unicode_literals import six from prompt_toolkit.application.current import get_app from prompt_toolkit.cache import memoized from prompt_toolkit.enums import EditingMode from .base import Condition __all__ = [ 'has_arg', 'has_completions', 'completion_is_selected', 'has_focus', 'buffer_has_focus', 'has_selection', 'has_validation_error', 'is_done', 'is_read_only', 'is_multiline', 'renderer_height_is_known', 'in_editing_mode', 'in_paste_mode', 'vi_mode', 'vi_navigation_mode', 'vi_insert_mode', 'vi_insert_multiple_mode', 'vi_replace_mode', 'vi_selection_mode', 'vi_waiting_for_text_object_mode', 'vi_digraph_mode', 'vi_recording_macro', 'emacs_mode', 'emacs_insert_mode', 'emacs_selection_mode', 'is_searching', 'control_is_searchable', 'vi_search_direction_reversed', ] @memoized() def has_focus(value): """ Enable when this buffer has the focus. """ from prompt_toolkit.buffer import Buffer from prompt_toolkit.layout.controls import UIControl from prompt_toolkit.layout.containers import to_container, Window from prompt_toolkit.layout import walk if isinstance(value, six.text_type): def test(): return get_app().current_buffer.name == value elif isinstance(value, Buffer): def test(): return get_app().current_buffer == value elif isinstance(value, UIControl): def test(): return get_app().layout.current_control == value else: value = to_container(value) if isinstance(value, Window): def test(): return get_app().layout.current_window == value else: def test(): # Consider focused when any window inside this container is # focused. current_window = get_app().layout.current_window for c in walk(value): if isinstance(c, Window) and c == current_window: return True return False @Condition def has_focus_filter(): return test() return has_focus_filter @Condition def buffer_has_focus(): """ Enabled when the currently focused control is a `BufferControl`. """ return get_app().layout.buffer_has_focus @Condition def has_selection(): """ Enable when the current buffer has a selection. """ return bool(get_app().current_buffer.selection_state) @Condition def has_completions(): """ Enable when the current buffer has completions. """ state = get_app().current_buffer.complete_state return state is not None and len(state.completions) > 0 @Condition def completion_is_selected(): """ True when the user selected a completion. """ complete_state = get_app().current_buffer.complete_state return (complete_state is not None and complete_state.current_completion is not None) @Condition def is_read_only(): """ True when the current buffer is read only. """ return get_app().current_buffer.read_only() @Condition def is_multiline(): """ True when the current buffer has been marked as multiline. """ return get_app().current_buffer.multiline() @Condition def has_validation_error(): " Current buffer has validation error. " return get_app().current_buffer.validation_error is not None @Condition def has_arg(): " Enable when the input processor has an 'arg'. " return get_app().key_processor.arg is not None @Condition def is_done(): """ True when the CLI is returning, aborting or exiting. """ return get_app().is_done @Condition def renderer_height_is_known(): """ Only True when the renderer knows it's real height. (On VT100 terminals, we have to wait for a CPR response, before we can be sure of the available height between the cursor position and the bottom of the terminal. And usually it's nicer to wait with drawing bottom toolbars until we receive the height, in order to avoid flickering -- first drawing somewhere in the middle, and then again at the bottom.) """ return get_app().renderer.height_is_known @memoized() def in_editing_mode(editing_mode): " Check whether a given editing mode is active. (Vi or Emacs.) " @Condition def in_editing_mode_filter(): return get_app().editing_mode == editing_mode return in_editing_mode_filter @Condition def in_paste_mode(): return get_app().paste_mode() @Condition def vi_mode(): return get_app().editing_mode == EditingMode.VI @Condition def vi_navigation_mode(): " Active when the set for Vi navigation key bindings are active. " from prompt_toolkit.key_binding.vi_state import InputMode app = get_app() if (app.editing_mode != EditingMode.VI or app.vi_state.operator_func or app.vi_state.waiting_for_digraph or app.current_buffer.selection_state): return False return (app.vi_state.input_mode == InputMode.NAVIGATION or app.vi_state.temporary_navigation_mode or app.current_buffer.read_only()) @Condition def vi_insert_mode(): from prompt_toolkit.key_binding.vi_state import InputMode app = get_app() if (app.editing_mode != EditingMode.VI or app.vi_state.operator_func or app.vi_state.waiting_for_digraph or app.current_buffer.selection_state or app.vi_state.temporary_navigation_mode or app.current_buffer.read_only()): return False return app.vi_state.input_mode == InputMode.INSERT @Condition def vi_insert_multiple_mode(): from prompt_toolkit.key_binding.vi_state import InputMode app = get_app() if (app.editing_mode != EditingMode.VI or app.vi_state.operator_func or app.vi_state.waiting_for_digraph or app.current_buffer.selection_state or app.vi_state.temporary_navigation_mode or app.current_buffer.read_only()): return False return app.vi_state.input_mode == InputMode.INSERT_MULTIPLE @Condition def vi_replace_mode(): from prompt_toolkit.key_binding.vi_state import InputMode app = get_app() if (app.editing_mode != EditingMode.VI or app.vi_state.operator_func or app.vi_state.waiting_for_digraph or app.current_buffer.selection_state or app.vi_state.temporary_navigation_mode or app.current_buffer.read_only()): return False return app.vi_state.input_mode == InputMode.REPLACE @Condition def vi_selection_mode(): app = get_app() if app.editing_mode != EditingMode.VI: return False return bool(app.current_buffer.selection_state) @Condition def vi_waiting_for_text_object_mode(): app = get_app() if app.editing_mode != EditingMode.VI: return False return app.vi_state.operator_func is not None @Condition def vi_digraph_mode(): app = get_app() if app.editing_mode != EditingMode.VI: return False return app.vi_state.waiting_for_digraph @Condition def vi_recording_macro(): " When recording a Vi macro. " app = get_app() if app.editing_mode != EditingMode.VI: return False return app.vi_state.recording_register is not None @Condition def emacs_mode(): " When the Emacs bindings are active. " return get_app().editing_mode == EditingMode.EMACS @Condition def emacs_insert_mode(): app = get_app() if (app.editing_mode != EditingMode.EMACS or app.current_buffer.selection_state or app.current_buffer.read_only()): return False return True @Condition def emacs_selection_mode(): app = get_app() return (app.editing_mode == EditingMode.EMACS and app.current_buffer.selection_state) @Condition def is_searching(): " When we are searching. " app = get_app() return app.layout.is_searching @Condition def control_is_searchable(): " When the current UIControl is searchable. " from prompt_toolkit.layout.controls import BufferControl control = get_app().layout.current_control return (isinstance(control, BufferControl) and control.search_buffer_control is not None) @Condition def vi_search_direction_reversed(): " When the '/' and '?' key bindings for Vi-style searching have been reversed. " return get_app().reverse_vi_search_direction() prompt_toolkit-2.0.10/prompt_toolkit/filters/base.py0000644000175100017510000001245213545407204024342 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from prompt_toolkit.utils import test_callable_args __all__ = [ 'Filter', 'Never', 'Always', 'Condition', ] class Filter(with_metaclass(ABCMeta, object)): """ Base class for any filter to activate/deactivate a feature, depending on a condition. The return value of ``__call__`` will tell if the feature should be active. """ @abstractmethod def __call__(self): """ The actual call to evaluate the filter. """ return True def __and__(self, other): """ Chaining of filters using the & operator. """ return _and_cache[self, other] def __or__(self, other): """ Chaining of filters using the | operator. """ return _or_cache[self, other] def __invert__(self): """ Inverting of filters using the ~ operator. """ return _invert_cache[self] def __bool__(self): """ By purpose, we don't allow bool(...) operations directly on a filter, because the meaning is ambiguous. Executing a filter has to be done always by calling it. Providing defaults for `None` values should be done through an `is None` check instead of for instance ``filter1 or Always()``. """ raise ValueError('The truth value of a Filter is ambiguous. ' 'Instead, call it as a function.') __nonzero__ = __bool__ # For Python 2. class _AndCache(dict): """ Cache for And operation between filters. (Filter classes are stateless, so we can reuse them.) Note: This could be a memory leak if we keep creating filters at runtime. If that is True, the filters should be weakreffed (not the tuple of filters), and tuples should be removed when one of these filters is removed. In practise however, there is a finite amount of filters. """ def __missing__(self, filters): a, b = filters assert isinstance(b, Filter), 'Expecting filter, got %r' % b if isinstance(b, Always) or isinstance(a, Never): return a elif isinstance(b, Never) or isinstance(a, Always): return b result = _AndList(filters) self[filters] = result return result class _OrCache(dict): """ Cache for Or operation between filters. """ def __missing__(self, filters): a, b = filters assert isinstance(b, Filter), 'Expecting filter, got %r' % b if isinstance(b, Always) or isinstance(a, Never): return b elif isinstance(b, Never) or isinstance(a, Always): return a result = _OrList(filters) self[filters] = result return result class _InvertCache(dict): """ Cache for inversion operator. """ def __missing__(self, filter): result = _Invert(filter) self[filter] = result return result _and_cache = _AndCache() _or_cache = _OrCache() _invert_cache = _InvertCache() class _AndList(Filter): """ Result of &-operation between several filters. """ def __init__(self, filters): all_filters = [] for f in filters: if isinstance(f, _AndList): # Turn nested _AndLists into one. all_filters.extend(f.filters) else: all_filters.append(f) self.filters = all_filters def __call__(self): return all(f() for f in self.filters) def __repr__(self): return '&'.join(repr(f) for f in self.filters) class _OrList(Filter): """ Result of |-operation between several filters. """ def __init__(self, filters): all_filters = [] for f in filters: if isinstance(f, _OrList): # Turn nested _OrLists into one. all_filters.extend(f.filters) else: all_filters.append(f) self.filters = all_filters def __call__(self): return any(f() for f in self.filters) def __repr__(self): return '|'.join(repr(f) for f in self.filters) class _Invert(Filter): """ Negation of another filter. """ def __init__(self, filter): self.filter = filter def __call__(self): return not self.filter() def __repr__(self): return '~%r' % self.filter class Always(Filter): """ Always enable feature. """ def __call__(self): return True def __invert__(self): return Never() class Never(Filter): """ Never enable feature. """ def __call__(self): return False def __invert__(self): return Always() class Condition(Filter): """ Turn any callable into a Filter. The callable is supposed to not take any arguments. This can be used as a decorator:: @Condition def feature_is_active(): # `feature_is_active` becomes a Filter. return True :param func: Callable which takes no inputs and returns a boolean. """ def __init__(self, func): assert callable(func) assert test_callable_args(func, []) self.func = func def __call__(self): return self.func() def __repr__(self): return 'Condition(%r)' % self.func prompt_toolkit-2.0.10/prompt_toolkit/filters/cli.py0000644000175100017510000000352213545407204024175 0ustar jonathanjonathan00000000000000""" For backwards-compatibility. keep this file. (Many people are going to have key bindings that rely on this file.) """ from __future__ import unicode_literals from .app import * __all__ = [ # Old names. 'HasArg', 'HasCompletions', 'HasFocus', 'HasSelection', 'HasValidationError', 'IsDone', 'IsReadOnly', 'IsMultiline', 'RendererHeightIsKnown', 'InEditingMode', 'InPasteMode', 'ViMode', 'ViNavigationMode', 'ViInsertMode', 'ViInsertMultipleMode', 'ViReplaceMode', 'ViSelectionMode', 'ViWaitingForTextObjectMode', 'ViDigraphMode', 'EmacsMode', 'EmacsInsertMode', 'EmacsSelectionMode', 'IsSearching', 'HasSearch', 'ControlIsSearchable', ] # Keep the original classnames for backwards compatibility. HasValidationError = lambda: has_validation_error HasArg = lambda: has_arg IsDone = lambda: is_done RendererHeightIsKnown = lambda: renderer_height_is_known ViNavigationMode = lambda: vi_navigation_mode InPasteMode = lambda: in_paste_mode EmacsMode = lambda: emacs_mode EmacsInsertMode = lambda: emacs_insert_mode ViMode = lambda: vi_mode IsSearching = lambda: is_searching HasSearch = lambda: is_searching ControlIsSearchable = lambda: control_is_searchable EmacsSelectionMode = lambda: emacs_selection_mode ViDigraphMode = lambda: vi_digraph_mode ViWaitingForTextObjectMode = lambda: vi_waiting_for_text_object_mode ViSelectionMode = lambda: vi_selection_mode ViReplaceMode = lambda: vi_replace_mode ViInsertMultipleMode = lambda: vi_insert_multiple_mode ViInsertMode = lambda: vi_insert_mode HasSelection = lambda: has_selection HasCompletions = lambda: has_completions IsReadOnly = lambda: is_read_only IsMultiline = lambda: is_multiline HasFocus = has_focus # No lambda here! (Has_focus is callable that returns a callable.) InEditingMode = in_editing_mode prompt_toolkit-2.0.10/prompt_toolkit/filters/utils.py0000644000175100017510000000131713545407204024566 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import Always, Filter, Never __all__ = [ 'to_filter', 'is_true', ] _always = Always() _never = Never() def to_filter(bool_or_filter): """ Accept both booleans and Filters as input and turn it into a Filter. """ if not isinstance(bool_or_filter, (bool, Filter)): raise TypeError('Expecting a bool or a Filter instance. Got %r' % bool_or_filter) return { True: _always, False: _never, }.get(bool_or_filter, bool_or_filter) def is_true(value): """ Test whether `value` is True. In case of a Filter, call it. :param value: Boolean or `Filter` instance. """ return to_filter(value)() prompt_toolkit-2.0.10/prompt_toolkit/formatted_text/0000755000175100017510000000000013545410361024431 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/formatted_text/__init__.py0000644000175100017510000000245713545407204026554 0ustar jonathanjonathan00000000000000""" Many places in prompt_toolkit can take either plain text, or formatted text. For instance the :func:`~prompt_toolkit.shortcuts.prompt` function takes either plain text or formatted text for the prompt. The :class:`~prompt_toolkit.layout.FormattedTextControl` can also take either plain text or formatted text. In any case, there is an input that can either be just plain text (a string), an :class:`.HTML` object, an :class:`.ANSI` object or a sequence of `(style_string, text)` tuples. The :func:`.to_formatted_text` conversion function takes any of these and turns all of them into such a tuple sequence. """ from __future__ import unicode_literals from .ansi import ANSI from .base import ( FormattedText, Template, is_formatted_text, merge_formatted_text, to_formatted_text, ) from .html import HTML from .pygments import PygmentsTokens from .utils import ( fragment_list_len, fragment_list_to_text, fragment_list_width, split_lines, ) __all__ = [ # Base. 'to_formatted_text', 'is_formatted_text', 'Template', 'merge_formatted_text', 'FormattedText', # HTML. 'HTML', # ANSI. 'ANSI', # Pygments. 'PygmentsTokens', # Utils. 'fragment_list_len', 'fragment_list_width', 'fragment_list_to_text', 'split_lines', ] prompt_toolkit-2.0.10/prompt_toolkit/formatted_text/ansi.py0000644000175100017510000001724013545407204025743 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.output.vt100 import BG_ANSI_COLORS, FG_ANSI_COLORS from prompt_toolkit.output.vt100 import _256_colors as _256_colors_table from .base import FormattedText __all__ = [ 'ANSI', 'ansi_escape', ] class ANSI(object): """ ANSI formatted text. Take something ANSI escaped text, for use as a formatted string. E.g. :: ANSI('\\x1b[31mhello \\x1b[32mworld') Characters between ``\\001`` and ``\\002`` are supposed to have a zero width when printed, but these are literally sent to the terminal output. This can be used for instance, for inserting Final Term prompt commands. They will be translated into a prompt_toolkit '[ZeroWidthEscape]' fragment. """ def __init__(self, value): self.value = value self._formatted_text = FormattedText() # Default style attributes. self._color = None self._bgcolor = None self._bold = False self._underline = False self._italic = False self._blink = False self._reverse = False self._hidden = False # Process received text. parser = self._parse_corot() parser.send(None) for c in value: parser.send(c) def _parse_corot(self): """ Coroutine that parses the ANSI escape sequences. """ style = '' formatted_text = self._formatted_text while True: csi = False c = yield # Everything between \001 and \002 should become a ZeroWidthEscape. if c == '\001': escaped_text = '' while c != '\002': c = yield if c == '\002': formatted_text.append(('[ZeroWidthEscape]', escaped_text)) c = yield break else: escaped_text += c if c == '\x1b': # Start of color escape sequence. square_bracket = yield if square_bracket == '[': csi = True else: continue elif c == '\x9b': csi = True if csi: # Got a CSI sequence. Color codes are following. current = '' params = [] while True: char = yield if char.isdigit(): current += char else: params.append(min(int(current or 0), 9999)) if char == ';': current = '' elif char == 'm': # Set attributes and token. self._select_graphic_rendition(params) style = self._create_style_string() break else: # Ignore unsupported sequence. break else: # Add current character. # NOTE: At this point, we could merge the current character # into the previous tuple if the style did not change, # however, it's not worth the effort given that it will # be "Exploded" once again when it's rendered to the # output. formatted_text.append((style, c)) def _select_graphic_rendition(self, attrs): """ Taken a list of graphics attributes and apply changes. """ if not attrs: attrs = [0] else: attrs = list(attrs[::-1]) while attrs: attr = attrs.pop() if attr in _fg_colors: self._color = _fg_colors[attr] elif attr in _bg_colors: self._bgcolor = _bg_colors[attr] elif attr == 1: self._bold = True elif attr == 3: self._italic = True elif attr == 4: self._underline = True elif attr == 5: self._blink = True elif attr == 6: self._blink = True # Fast blink. elif attr == 7: self._reverse = True elif attr == 8: self._hidden = True elif attr == 22: self._bold = False elif attr == 23: self._italic = False elif attr == 24: self._underline = False elif attr == 25: self._blink = False elif attr == 27: self._reverse = False elif not attr: self._color = None self._bgcolor = None self._bold = False self._underline = False self._italic = False self._blink = False self._reverse = False self._hidden = False elif attr in (38, 48) and len(attrs) > 1: n = attrs.pop() # 256 colors. if n == 5 and len(attrs) >= 1: if attr == 38: m = attrs.pop() self._color = _256_colors.get(m) elif attr == 48: m = attrs.pop() self._bgcolor = _256_colors.get(m) # True colors. if n == 2 and len(attrs) >= 3: try: color_str = '%02x%02x%02x' % ( attrs.pop(), attrs.pop(), attrs.pop()) except IndexError: pass else: if attr == 38: self._color = color_str elif attr == 48: self._bgcolor = color_str def _create_style_string(self): """ Turn current style flags into a string for usage in a formatted text. """ result = [] if self._color: result.append(self._color) if self._bgcolor: result.append('bg:' + self._bgcolor) if self._bold: result.append('bold') if self._underline: result.append('underline') if self._italic: result.append('italic') if self._blink: result.append('blink') if self._reverse: result.append('reverse') if self._hidden: result.append('hidden') return ' '.join(result) def __repr__(self): return 'ANSI(%r)' % (self.value, ) def __pt_formatted_text__(self): return self._formatted_text def format(self, *args, **kwargs): """ Like `str.format`, but make sure that the arguments are properly escaped. (No ANSI escapes can be injected.) """ # Escape all the arguments. args = [ansi_escape(a) for a in args] kwargs = dict((k, ansi_escape(v)) for k, v in kwargs.items()) return ANSI(self.value.format(*args, **kwargs)) # Mapping of the ANSI color codes to their names. _fg_colors = dict((v, k) for k, v in FG_ANSI_COLORS.items()) _bg_colors = dict((v, k) for k, v in BG_ANSI_COLORS.items()) # Mapping of the escape codes for 256colors to their 'ffffff' value. _256_colors = {} for i, (r, g, b) in enumerate(_256_colors_table.colors): _256_colors[i] = '#%02x%02x%02x' % (r, g, b) def ansi_escape(text): """ Replace characters with a special meaning. """ return text.replace('\x1b', '?').replace('\b', '?') prompt_toolkit-2.0.10/prompt_toolkit/formatted_text/base.py0000644000175100017510000001071213545407204025720 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import six __all__ = [ 'to_formatted_text', 'is_formatted_text', 'Template', 'merge_formatted_text', 'FormattedText', ] def to_formatted_text(value, style='', auto_convert=False): """ Convert the given value (which can be formatted text) into a list of text fragments. (Which is the canonical form of formatted text.) The outcome is always a `FormattedText` instance, which is a list of (style, text) tuples. It can take an `HTML` object, a plain text string, or anything that implements `__pt_formatted_text__`. :param style: An additional style string which is applied to all text fragments. :param auto_convert: If `True`, also accept other types, and convert them to a string first. """ assert isinstance(style, six.text_type) if value is None: result = [] elif isinstance(value, six.text_type): result = [('', value)] elif isinstance(value, list): if len(value): assert isinstance(value[0][0], six.text_type), \ 'Expecting string, got: %r' % (value[0][0], ) assert isinstance(value[0][1], six.text_type), \ 'Expecting string, got: %r' % (value[0][1], ) result = value elif hasattr(value, '__pt_formatted_text__'): result = value.__pt_formatted_text__() elif callable(value): return to_formatted_text(value(), style=style) elif auto_convert: result = [('', '{}'.format(value))] else: raise ValueError('No formatted text. Expecting a unicode object, ' 'HTML, ANSI or a FormattedText instance. Got %r' % value) # Apply extra style. if style: try: result = [(style + ' ' + k, v) for k, v in result] except ValueError: # Too many values to unpack: # If the above failed, try the slower version (almost twice as # slow) which supports multiple items. This is used in the # `to_formatted_text` call in `FormattedTextControl` which also # accepts (style, text, mouse_handler) tuples. result = [(style + ' ' + item[0], ) + item[1:] for item in result] # Make sure the result is wrapped in a `FormattedText`. Among other # reasons, this is important for `print_formatted_text` to work correctly # and distinguish between lists and formatted text. if isinstance(result, FormattedText): return result else: return FormattedText(result) def is_formatted_text(value): """ Check whether the input is valid formatted text (for use in assert statements). In case of a callable, it doesn't check the return type. """ if callable(value): return True if isinstance(value, (six.text_type, list)): return True if hasattr(value, '__pt_formatted_text__'): return True return False class FormattedText(list): """ A list of ``(style, text)`` tuples. (In some situations, this can also be ``(style, text, mouse_handler)`` tuples.) """ def __pt_formatted_text__(self): return self def __repr__(self): return 'FormattedText(%s)' % ( super(FormattedText, self).__repr__()) class Template(object): """ Template for string interpolation with formatted text. Example:: Template(' ... {} ... ').format(HTML(...)) :param text: Plain text. """ def __init__(self, text): assert isinstance(text, six.text_type) assert '{0}' not in text self.text = text def format(self, *values): assert all(is_formatted_text(v) for v in values) def get_result(): # Split the template in parts. parts = self.text.split('{}') assert len(parts) - 1 == len(values) result = FormattedText() for part, val in zip(parts, values): result.append(('', part)) result.extend(to_formatted_text(val)) result.append(('', parts[-1])) return result return get_result def merge_formatted_text(items): """ Merge (Concatenate) several pieces of formatted text together. """ assert all(is_formatted_text(v) for v in items) def _merge_formatted_text(): result = FormattedText() for i in items: result.extend(to_formatted_text(i)) return result return _merge_formatted_text prompt_toolkit-2.0.10/prompt_toolkit/formatted_text/html.py0000644000175100017510000000724713545407204025763 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import xml.dom.minidom as minidom import six from .base import FormattedText __all__ = [ 'HTML' ] class HTML(object): """ HTML formatted text. Take something HTML-like, for use as a formatted string. :: # Turn something into red. HTML('') # Italic, bold and underline. HTML('...') HTML('...') HTML('...') All HTML elements become available as a "class" in the style sheet. E.g. ``...`` can be styled, by setting a style for ``username``. """ def __init__(self, value): assert isinstance(value, six.text_type) self.value = value document = minidom.parseString('%s' % (value, )) result = [] name_stack = [] fg_stack = [] bg_stack = [] def get_current_style(): " Build style string for current node. " parts = [] if name_stack: parts.append('class:' + ','.join(name_stack)) if fg_stack: parts.append('fg:' + fg_stack[-1]) if bg_stack: parts.append('bg:' + bg_stack[-1]) return ' '.join(parts) def process_node(node): " Process node recursively. " for child in node.childNodes: if child.nodeType == child.TEXT_NODE: result.append((get_current_style(), child.data)) else: add_to_name_stack = child.nodeName not in ('#document', 'html-root', 'style') fg = bg = '' for k, v in child.attributes.items(): if k == 'fg': fg = v if k == 'bg': bg = v if k == 'color': fg = v # Alias for 'fg'. # Check for spaces in attributes. This would result in # invalid style strings otherwise. if ' ' in fg: raise ValueError('"fg" attribute contains a space.') if ' ' in bg: raise ValueError('"bg" attribute contains a space.') if add_to_name_stack: name_stack.append(child.nodeName) if fg: fg_stack.append(fg) if bg: bg_stack.append(bg) process_node(child) if add_to_name_stack: name_stack.pop() if fg: fg_stack.pop() if bg: bg_stack.pop() process_node(document) self.formatted_text = FormattedText(result) def __repr__(self): return 'HTML(%r)' % (self.value, ) def __pt_formatted_text__(self): return self.formatted_text def format(self, *args, **kwargs): """ Like `str.format`, but make sure that the arguments are properly escaped. """ # Escape all the arguments. args = [html_escape(a) for a in args] kwargs = dict((k, html_escape(v)) for k, v in kwargs.items()) return HTML(self.value.format(*args, **kwargs)) def __mod__(self, value): """ HTML('%s') % value """ if not isinstance(value, tuple): value = (value, ) value = tuple(html_escape(i) for i in value) return HTML(self.value % value) def html_escape(text): # The string interpolation functions also take integers and other types. # Convert to string first. if not isinstance(text, six.text_type): text = '{}'.format(text) return text.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') prompt_toolkit-2.0.10/prompt_toolkit/formatted_text/pygments.py0000644000175100017510000000130213545407204026647 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.styles.pygments import pygments_token_to_classname from .base import FormattedText __all__ = [ 'PygmentsTokens', ] class PygmentsTokens(object): """ Turn a pygments token list into a list of prompt_toolkit text fragments (``(style_str, text)`` tuples). """ def __init__(self, token_list): assert isinstance(token_list, list), 'Got %r' % (token_list, ) self.token_list = token_list def __pt_formatted_text__(self): result = FormattedText() for token, text in self.token_list: result.append(('class:' + pygments_token_to_classname(token), text)) return result prompt_toolkit-2.0.10/prompt_toolkit/formatted_text/utils.py0000644000175100017510000000614013545407204026146 0ustar jonathanjonathan00000000000000""" Utilities for manipulating formatted text. When ``to_formatted_text`` has been called, we get a list of ``(style, text)`` tuples. This file contains functions for manipulating such a list. """ from __future__ import unicode_literals from prompt_toolkit.utils import get_cwidth __all__ = [ 'fragment_list_len', 'fragment_list_width', 'fragment_list_to_text', 'split_lines', ] def fragment_list_len(fragments): """ Return the amount of characters in this text fragment list. :param fragments: List of ``(style_str, text)`` or ``(style_str, text, mouse_handler)`` tuples. """ ZeroWidthEscape = '[ZeroWidthEscape]' return sum(len(item[1]) for item in fragments if ZeroWidthEscape not in item[0]) def fragment_list_width(fragments): """ Return the character width of this text fragment list. (Take double width characters into account.) :param fragments: List of ``(style_str, text)`` or ``(style_str, text, mouse_handler)`` tuples. """ ZeroWidthEscape = '[ZeroWidthEscape]' return sum(get_cwidth(c) for item in fragments for c in item[1] if ZeroWidthEscape not in item[0]) def fragment_list_to_text(fragments): """ Concatenate all the text parts again. :param fragments: List of ``(style_str, text)`` or ``(style_str, text, mouse_handler)`` tuples. """ ZeroWidthEscape = '[ZeroWidthEscape]' return ''.join(item[1] for item in fragments if ZeroWidthEscape not in item[0]) def split_lines(fragments): """ Take a single list of (style_str, text) tuples and yield one such list for each line. Just like str.split, this will yield at least one item. :param fragments: List of (style_str, text) or (style_str, text, mouse_handler) tuples. """ line = [] for item in fragments: # For (style_str, text) tuples. if len(item) == 2: style, string = item parts = string.split('\n') for part in parts[:-1]: if part: line.append((style, part)) yield line line = [] line.append((style, parts[-1])) # Note that parts[-1] can be empty, and that's fine. It happens # in the case of [('[SetCursorPosition]', '')]. # For (style_str, text, mouse_handler) tuples. # I know, partly copy/paste, but understandable and more efficient # than many tests. else: style, string, mouse_handler = item parts = string.split('\n') for part in parts[:-1]: if part: line.append((style, part, mouse_handler)) yield line line = [] line.append((style, parts[-1], mouse_handler)) # Always yield the last line, even when this is an empty line. This ensures # that when `fragments` ends with a newline character, an additional empty # line is yielded. (Otherwise, there's no way to differentiate between the # cases where `fragments` does and doesn't end with a newline.) yield line prompt_toolkit-2.0.10/prompt_toolkit/history.py0000644000175100017510000001455013545407204023462 0ustar jonathanjonathan00000000000000""" Implementations for the history of a `Buffer`. NOTE: Notice that there is no `DynamicHistory`. This doesn't work well, because the `Buffer` needs to be able to attach an event handler to the event when a history entry is loaded. This loading can be done asynchronously and making the history swappable would probably break this. """ from __future__ import unicode_literals import datetime import os from abc import ABCMeta, abstractmethod from six import text_type, with_metaclass from .eventloop import ( AsyncGeneratorItem, From, consume_async_generator, ensure_future, generator_to_async_generator, ) from .utils import Event __all__ = [ 'History', 'ThreadedHistory', 'DummyHistory', 'FileHistory', 'InMemoryHistory', ] class History(with_metaclass(ABCMeta, object)): """ Base ``History`` class. This also includes abstract methods for loading/storing history. """ def __init__(self): # In memory storage for strings. self._loading = False self._loaded_strings = [] self._item_loaded = Event(self) def _start_loading(self): """ Consume the asynchronous generator: `load_history_strings_async`. This is only called once, because once the history is loaded, we don't have to load it again. """ def add_string(string): " Got one string from the asynchronous history generator. " self._loaded_strings.insert(0, string) self._item_loaded.fire() yield From(consume_async_generator( self.load_history_strings_async(), cancel=lambda: False, # Right now, we don't have cancellation # of history loading in any way. item_callback=add_string)) # # Methods expected by `Buffer`. # def start_loading(self): " Start loading the history. " if not self._loading: self._loading = True ensure_future(self._start_loading()) def get_item_loaded_event(self): " Event which is triggered when a new item is loaded. " return self._item_loaded def get_strings(self): """ Get the strings from the history that are loaded so far. """ return self._loaded_strings def append_string(self, string): " Add string to the history. " self._loaded_strings.append(string) self.store_string(string) # # Implementation for specific backends. # @abstractmethod def load_history_strings(self): """ This should be a generator that yields `str` instances. It should yield the most recent items first, because they are the most important. (The history can already be used, even when it's only partially loaded.) """ while False: yield def load_history_strings_async(self): """ Asynchronous generator for history strings. (Probably, you won't have to override this.) This should return an iterable that can yield both `str` and `Future` objects. The `str` objects have to be wrapped in a `AsyncGeneratorItem` object. If we drop Python 2 support in the future, this could become a true asynchronous generator. """ for item in self.load_history_strings(): assert isinstance(item, text_type) yield AsyncGeneratorItem(item) @abstractmethod def store_string(self, string): """ Store the string in persistent storage. """ class ThreadedHistory(History): """ Wrapper that runs the `load_history_strings` generator in a thread. Use this to increase the start-up time of prompt_toolkit applications. History entries are available as soon as they are loaded. We don't have to wait for everything to be loaded. """ def __init__(self, history=None): assert isinstance(history, History), 'Got %r' % (history, ) self.history = history super(ThreadedHistory, self).__init__() def load_history_strings_async(self): """ Asynchronous generator of completions. This yields both Future and Completion objects. """ return generator_to_async_generator( self.history.load_history_strings) # All of the following are proxied to `self.history`. def load_history_strings(self): return self.history.load_history_strings() def store_string(self, string): self.history.store_string(string) def __repr__(self): return 'ThreadedHistory(%r)' % (self.history, ) class InMemoryHistory(History): """ :class:`.History` class that keeps a list of all strings in memory. """ def load_history_strings(self): return [] def store_string(self, string): pass class DummyHistory(History): """ :class:`.History` object that doesn't remember anything. """ def load_history_strings(self): return [] def store_string(self, string): pass def append_string(self, string): # Don't remember this. pass class FileHistory(History): """ :class:`.History` class that stores all strings in a file. """ def __init__(self, filename): self.filename = filename super(FileHistory, self).__init__() def load_history_strings(self): strings = [] lines = [] def add(): if lines: # Join and drop trailing newline. string = ''.join(lines)[:-1] strings.append(string) if os.path.exists(self.filename): with open(self.filename, 'rb') as f: for line in f: line = line.decode('utf-8') if line.startswith('+'): lines.append(line[1:]) else: add() lines = [] add() # Reverse the order, because newest items have to go first. return reversed(strings) def store_string(self, string): # Save to file. with open(self.filename, 'ab') as f: def write(t): f.write(t.encode('utf-8')) write('\n# %s\n' % datetime.datetime.now()) for line in string.split('\n'): write('+%s\n' % line) prompt_toolkit-2.0.10/prompt_toolkit/input/0000755000175100017510000000000013545410361022537 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/input/__init__.py0000644000175100017510000000044713545407204024657 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import DummyInput, Input from .defaults import create_input, get_default_input, set_default_input __all__ = [ # Base. 'Input', 'DummyInput', # Defaults. 'create_input', 'get_default_input', 'set_default_input', ] prompt_toolkit-2.0.10/prompt_toolkit/input/ansi_escape_sequences.py0000644000175100017510000001562613545407204027452 0ustar jonathanjonathan00000000000000""" Mappings from VT100 (ANSI) escape sequences to the corresponding prompt_toolkit keys. """ from __future__ import unicode_literals from ..keys import Keys __all__ = [ 'ANSI_SEQUENCES', 'REVERSE_ANSI_SEQUENCES', ] # Mapping of vt100 escape codes to Keys. ANSI_SEQUENCES = { '\x00': Keys.ControlAt, # Control-At (Also for Ctrl-Space) '\x01': Keys.ControlA, # Control-A (home) '\x02': Keys.ControlB, # Control-B (emacs cursor left) '\x03': Keys.ControlC, # Control-C (interrupt) '\x04': Keys.ControlD, # Control-D (exit) '\x05': Keys.ControlE, # Control-E (end) '\x06': Keys.ControlF, # Control-F (cursor forward) '\x07': Keys.ControlG, # Control-G '\x08': Keys.ControlH, # Control-H (8) (Identical to '\b') '\x09': Keys.ControlI, # Control-I (9) (Identical to '\t') '\x0a': Keys.ControlJ, # Control-J (10) (Identical to '\n') '\x0b': Keys.ControlK, # Control-K (delete until end of line; vertical tab) '\x0c': Keys.ControlL, # Control-L (clear; form feed) '\x0d': Keys.ControlM, # Control-M (13) (Identical to '\r') '\x0e': Keys.ControlN, # Control-N (14) (history forward) '\x0f': Keys.ControlO, # Control-O (15) '\x10': Keys.ControlP, # Control-P (16) (history back) '\x11': Keys.ControlQ, # Control-Q '\x12': Keys.ControlR, # Control-R (18) (reverse search) '\x13': Keys.ControlS, # Control-S (19) (forward search) '\x14': Keys.ControlT, # Control-T '\x15': Keys.ControlU, # Control-U '\x16': Keys.ControlV, # Control-V '\x17': Keys.ControlW, # Control-W '\x18': Keys.ControlX, # Control-X '\x19': Keys.ControlY, # Control-Y (25) '\x1a': Keys.ControlZ, # Control-Z '\x1b': Keys.Escape, # Also Control-[ '\x1c': Keys.ControlBackslash, # Both Control-\ (also Ctrl-| ) '\x1d': Keys.ControlSquareClose, # Control-] '\x1e': Keys.ControlCircumflex, # Control-^ '\x1f': Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hyphen.) # ASCII Delete (0x7f) # Vt220 (and Linux terminal) send this when pressing backspace. We map this # to ControlH, because that will make it easier to create key bindings that # work everywhere, with the trade-off that it's no longer possible to # handle backspace and control-h individually for the few terminals that # support it. (Most terminals send ControlH when backspace is pressed.) # See: http://www.ibb.net/~anne/keyboard.html '\x7f': Keys.ControlH, '\x1b[A': Keys.Up, '\x1b[B': Keys.Down, '\x1b[C': Keys.Right, '\x1b[D': Keys.Left, '\x1b[H': Keys.Home, '\x1bOH': Keys.Home, '\x1b[F': Keys.End, '\x1bOF': Keys.End, '\x1b[3~': Keys.Delete, '\x1b[3;2~': Keys.ShiftDelete, # xterm, gnome-terminal. '\x1b[3;5~': Keys.ControlDelete, # xterm, gnome-terminal. '\x1b[1~': Keys.Home, # tmux '\x1b[4~': Keys.End, # tmux '\x1b[5~': Keys.PageUp, '\x1b[6~': Keys.PageDown, '\x1b[7~': Keys.Home, # xrvt '\x1b[8~': Keys.End, # xrvt '\x1b[Z': Keys.BackTab, # shift + tab '\x1b[2~': Keys.Insert, '\x1bOP': Keys.F1, '\x1bOQ': Keys.F2, '\x1bOR': Keys.F3, '\x1bOS': Keys.F4, '\x1b[[A': Keys.F1, # Linux console. '\x1b[[B': Keys.F2, # Linux console. '\x1b[[C': Keys.F3, # Linux console. '\x1b[[D': Keys.F4, # Linux console. '\x1b[[E': Keys.F5, # Linux console. '\x1b[11~': Keys.F1, # rxvt-unicode '\x1b[12~': Keys.F2, # rxvt-unicode '\x1b[13~': Keys.F3, # rxvt-unicode '\x1b[14~': Keys.F4, # rxvt-unicode '\x1b[15~': Keys.F5, '\x1b[17~': Keys.F6, '\x1b[18~': Keys.F7, '\x1b[19~': Keys.F8, '\x1b[20~': Keys.F9, '\x1b[21~': Keys.F10, '\x1b[23~': Keys.F11, '\x1b[24~': Keys.F12, '\x1b[25~': Keys.F13, '\x1b[26~': Keys.F14, '\x1b[28~': Keys.F15, '\x1b[29~': Keys.F16, '\x1b[31~': Keys.F17, '\x1b[32~': Keys.F18, '\x1b[33~': Keys.F19, '\x1b[34~': Keys.F20, # Xterm '\x1b[1;2P': Keys.F13, '\x1b[1;2Q': Keys.F14, # '\x1b[1;2R': Keys.F15, # Conflicts with CPR response. '\x1b[1;2S': Keys.F16, '\x1b[15;2~': Keys.F17, '\x1b[17;2~': Keys.F18, '\x1b[18;2~': Keys.F19, '\x1b[19;2~': Keys.F20, '\x1b[20;2~': Keys.F21, '\x1b[21;2~': Keys.F22, '\x1b[23;2~': Keys.F23, '\x1b[24;2~': Keys.F24, '\x1b[1;5A': Keys.ControlUp, # Cursor Mode '\x1b[1;5B': Keys.ControlDown, # Cursor Mode '\x1b[1;5C': Keys.ControlRight, # Cursor Mode '\x1b[1;5D': Keys.ControlLeft, # Cursor Mode '\x1b[1;2A': Keys.ShiftUp, '\x1b[1;2B': Keys.ShiftDown, '\x1b[1;2C': Keys.ShiftRight, '\x1b[1;2D': Keys.ShiftLeft, # Tmux sends following keystrokes when control+arrow is pressed, but for # Emacs ansi-term sends the same sequences for normal arrow keys. Consider # it a normal arrow press, because that's more important. '\x1bOA': Keys.Up, '\x1bOB': Keys.Down, '\x1bOC': Keys.Right, '\x1bOD': Keys.Left, '\x1b[5A': Keys.ControlUp, '\x1b[5B': Keys.ControlDown, '\x1b[5C': Keys.ControlRight, '\x1b[5D': Keys.ControlLeft, '\x1bOc': Keys.ControlRight, # rxvt '\x1bOd': Keys.ControlLeft, # rxvt # Tmux (Win32 subsystem) sends the following scroll events. '\x1b[62~': Keys.ScrollUp, '\x1b[63~': Keys.ScrollDown, '\x1b[200~': Keys.BracketedPaste, # Start of bracketed paste. # Meta + arrow keys. Several terminals handle this differently. # The following sequences are for xterm and gnome-terminal. # (Iterm sends ESC followed by the normal arrow_up/down/left/right # sequences, and the OSX Terminal sends ESCb and ESCf for "alt # arrow_left" and "alt arrow_right." We don't handle these # explicitly, in here, because would could not distinguish between # pressing ESC (to go to Vi navigation mode), followed by just the # 'b' or 'f' key. These combinations are handled in # the input processor.) '\x1b[1;3D': (Keys.Escape, Keys.Left), '\x1b[1;3C': (Keys.Escape, Keys.Right), '\x1b[1;3A': (Keys.Escape, Keys.Up), '\x1b[1;3B': (Keys.Escape, Keys.Down), # Option+arrow on (some?) Macs when using iTerm defaults # (see issue #483) '\x1b[1;9A': (Keys.Escape, Keys.Up), '\x1b[1;9B': (Keys.Escape, Keys.Down), '\x1b[1;9C': (Keys.Escape, Keys.Right), '\x1b[1;9D': (Keys.Escape, Keys.Left), # Sequences generated by numpad 5. Not sure what it means. (It doesn't # appear in 'infocmp'. Just ignore. '\x1b[E': Keys.Ignore, # Xterm. '\x1b[G': Keys.Ignore, # Linux console. } def _get_reverse_ansi_sequences(): """ Create a dictionary that maps prompt_toolkit keys back to the VT100 escape sequences. """ result = {} for sequence, key in ANSI_SEQUENCES.items(): if not isinstance(key, tuple): if key not in result: result[key] = sequence return result REVERSE_ANSI_SEQUENCES = _get_reverse_ansi_sequences() prompt_toolkit-2.0.10/prompt_toolkit/input/base.py0000644000175100017510000000546413545407204024036 0ustar jonathanjonathan00000000000000""" Abstraction of CLI Input. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod, abstractproperty from contextlib import contextmanager from six import with_metaclass __all__ = [ 'Input', 'DummyInput', ] class Input(with_metaclass(ABCMeta, object)): """ Abstraction for any input. An instance of this class can be given to the constructor of a :class:`~prompt_toolkit.application.Application` and will also be passed to the :class:`~prompt_toolkit.eventloop.base.EventLoop`. """ @abstractmethod def fileno(self): """ Fileno for putting this in an event loop. """ @abstractmethod def typeahead_hash(self): """ Identifier for storing type ahead key presses. """ @abstractmethod def read_keys(self): """ Return a list of Key objects which are read/parsed from the input. """ def flush_keys(self): """ Flush the underlying parser. and return the pending keys. (Used for vt100 input.) """ return [] def flush(self): " The event loop can call this when the input has to be flushed. " pass @property def responds_to_cpr(self): """ `True` if the `Application` can expect to receive a CPR response from here. """ return False @abstractproperty def closed(self): " Should be true when the input stream is closed. " return False @abstractmethod def raw_mode(self): """ Context manager that turns the input into raw mode. """ @abstractmethod def cooked_mode(self): """ Context manager that turns the input into cooked mode. """ @abstractmethod def attach(self, input_ready_callback): """ Return a context manager that makes this input active in the current event loop. """ @abstractmethod def detach(self): """ Return a context manager that makes sure that this input is not active in the current event loop. """ def close(self): " Close input. " pass class DummyInput(Input): """ Input for use in a `DummyApplication` """ def fileno(self): raise NotImplementedError def typeahead_hash(self): return 'dummy-%s' % id(self) def read_keys(self): return [] @property def closed(self): return True def raw_mode(self): return _dummy_context_manager() def cooked_mode(self): return _dummy_context_manager() def attach(self, input_ready_callback): return _dummy_context_manager() def detach(self): return _dummy_context_manager() @contextmanager def _dummy_context_manager(): yield prompt_toolkit-2.0.10/prompt_toolkit/input/defaults.py0000644000175100017510000000341313545407204024723 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import sys from prompt_toolkit.application.current import get_app from prompt_toolkit.eventloop.context import TaskLocal, TaskLocalNotSetError from prompt_toolkit.utils import is_windows from .base import Input __all__ = [ 'create_input', 'create_pipe_input', 'get_default_input', 'set_default_input', ] def create_input(stdin=None): stdin = stdin or sys.stdin if is_windows(): from .win32 import Win32Input return Win32Input(stdin) else: from .vt100 import Vt100Input return Vt100Input(stdin) def create_pipe_input(): """ Create an input pipe. This is mostly useful for unit testing. """ if is_windows(): from .win32_pipe import Win32PipeInput return Win32PipeInput() else: from .posix_pipe import PosixPipeInput return PosixPipeInput() _default_input = TaskLocal() def get_default_input(): """ Get the input class to be used by default. Called when creating a new Application(), when no `Input` has been passed. """ # Other create/return the default input. try: value = _default_input.get() except TaskLocalNotSetError: # If an application is already running, take the input from there. # (This is important for the "ENTER for continue" prompts after # executing system commands and displaying readline-style completions.) app = get_app(return_none=True) if app: return app.input return create_input() else: return value def set_default_input(input): """ Set the default `Input` class. (Used for instance, for the telnet submodule.) """ assert isinstance(input, Input) _default_input.set(input) prompt_toolkit-2.0.10/prompt_toolkit/input/posix_pipe.py0000644000175100017510000000320713545407204025274 0ustar jonathanjonathan00000000000000import os from ..utils import DummyContext from .vt100 import Vt100Input __all__ = [ 'PosixPipeInput', ] class PosixPipeInput(Vt100Input): """ Input that is send through a pipe. This is useful if we want to send the input programmatically into the application. Mostly useful for unit testing. Usage:: input = PosixPipeInput() input.send_text('inputdata') """ _id = 0 def __init__(self, text=''): self._r, self._w = os.pipe() class Stdin(object): def isatty(stdin): return True def fileno(stdin): return self._r super(PosixPipeInput, self).__init__(Stdin()) self.send_text(text) # Identifier for every PipeInput for the hash. self.__class__._id += 1 self._id = self.__class__._id @property def responds_to_cpr(self): return False def send_bytes(self, data): os.write(self._w, data) def send_text(self, data): " Send text to the input. " os.write(self._w, data.encode('utf-8')) def raw_mode(self): return DummyContext() def cooked_mode(self): return DummyContext() def close(self): " Close pipe fds. " os.close(self._r) os.close(self._w) # We should assign `None` to 'self._r` and 'self._w', # The event loop still needs to know the the fileno for this input in order # to properly remove it from the selectors. def typeahead_hash(self): """ This needs to be unique for every `PipeInput`. """ return 'pipe-input-%s' % (self._id, ) prompt_toolkit-2.0.10/prompt_toolkit/input/posix_utils.py0000644000175100017510000000767213545407204025511 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import os import select from codecs import getincrementaldecoder import six __all__ = [ 'PosixStdinReader', ] class PosixStdinReader(object): """ Wrapper around stdin which reads (nonblocking) the next available 1024 bytes and decodes it. Note that you can't be sure that the input file is closed if the ``read`` function returns an empty string. When ``errors=ignore`` is passed, ``read`` can return an empty string if all malformed input was replaced by an empty string. (We can't block here and wait for more input.) So, because of that, check the ``closed`` attribute, to be sure that the file has been closed. :param stdin_fd: File descriptor from which we read. :param errors: Can be 'ignore', 'strict' or 'replace'. On Python3, this can be 'surrogateescape', which is the default. 'surrogateescape' is preferred, because this allows us to transfer unrecognised bytes to the key bindings. Some terminals, like lxterminal and Guake, use the 'Mxx' notation to send mouse events, where each 'x' can be any possible byte. """ # By default, we want to 'ignore' errors here. The input stream can be full # of junk. One occurrence of this that I had was when using iTerm2 on OS X, # with "Option as Meta" checked (You should choose "Option as +Esc".) def __init__(self, stdin_fd, errors=('ignore' if six.PY2 else 'surrogateescape')): assert isinstance(stdin_fd, int) self.stdin_fd = stdin_fd self.errors = errors # Create incremental decoder for decoding stdin. # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because # it could be that we are in the middle of a utf-8 byte sequence. self._stdin_decoder_cls = getincrementaldecoder('utf-8') self._stdin_decoder = self._stdin_decoder_cls(errors=errors) #: True when there is nothing anymore to read. self.closed = False def read(self, count=1024): # By default we choose a rather small chunk size, because reading # big amounts of input at once, causes the event loop to process # all these key bindings also at once without going back to the # loop. This will make the application feel unresponsive. """ Read the input and return it as a string. Return the text. Note that this can return an empty string, even when the input stream was not yet closed. This means that something went wrong during the decoding. """ if self.closed: return u'' # Check whether there is some input to read. `os.read` would block # otherwise. # (Actually, the event loop is responsible to make sure that this # function is only called when there is something to read, but for some # reason this happens in certain situations.) try: if not select.select([self.stdin_fd], [], [], 0)[0]: return u'' except IOError: # Happens for instance when the file descriptor was closed. # (We had this in ptterm, where the FD became ready, a callback was # scheduled, but in the meantime another callback closed it already.) self.closed = True # Note: the following works better than wrapping `self.stdin` like # `codecs.getreader('utf-8')(stdin)` and doing `read(1)`. # Somehow that causes some latency when the escape # character is pressed. (Especially on combination with the `select`.) try: data = os.read(self.stdin_fd, count) # Nothing more to read, stream is closed. if data == b'': self.closed = True return '' except OSError: # In case of SIGWINCH data = b'' return self._stdin_decoder.decode(data) prompt_toolkit-2.0.10/prompt_toolkit/input/typeahead.py0000644000175100017510000000507013545407204025061 0ustar jonathanjonathan00000000000000r""" Store input key strokes if we did read more than was required. The input classes `Vt100Input` and `Win32Input` read the input text in chunks of a few kilobytes. This means that if we read input from stdin, it could be that we read a couple of lines (with newlines in between) at once. This creates a problem: potentially, we read too much from stdin. Sometimes people paste several lines at once because they paste input in a REPL and expect each input() call to process one line. Or they rely on type ahead because the application can't keep up with the processing. However, we need to read input in bigger chunks. We need this mostly to support pasting of larger chunks of text. We don't want everything to become unresponsive because we: - read one character; - parse one character; - call the key binding, which does a string operation with one character; - and render the user interface. Doing text operations on single characters is very inefficient in Python, so we prefer to work on bigger chunks of text. This is why we have to read the input in bigger chunks. Further, line buffering is also not an option, because it doesn't work well in the architecture. We use lower level Posix APIs, that work better with the event loop and so on. In fact, there is also nothing that defines that only \n can accept the input, you could create a key binding for any key to accept the input. To support type ahead, this module will store all the key strokes that were read too early, so that they can be feed into to the next `prompt()` call or to the next prompt_toolkit `Application`. """ from __future__ import unicode_literals from collections import defaultdict from .base import Input __all__ = [ 'store_typeahead', 'get_typeahead', 'clear_typeahead', ] _buffer = defaultdict(list) # input hash -> list of key presses. def store_typeahead(input_obj, key_presses): """ Insert typeahead key presses for the given input. """ assert isinstance(input_obj, Input) assert isinstance(key_presses, list) global _buffer key = input_obj.typeahead_hash() _buffer[key].extend(key_presses) def get_typeahead(input_obj): """ Retrieve typeahead and reset the buffer for this input. """ assert isinstance(input_obj, Input) global _buffer key = input_obj.typeahead_hash() result = _buffer[key] _buffer[key] = [] return result def clear_typeahead(input_obj): """ Clear typeahead buffer. """ assert isinstance(input_obj, Input) global _buffer key = input_obj.typeahead_hash() _buffer[key] = [] prompt_toolkit-2.0.10/prompt_toolkit/input/vt100.py0000644000175100017510000002175613545407204024000 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import contextlib import io import os import sys import termios import tty from ..eventloop import get_event_loop from .base import Input from .posix_utils import PosixStdinReader from .vt100_parser import Vt100Parser __all__ = [ 'Vt100Input', 'raw_mode', 'cooked_mode', ] class Vt100Input(Input): """ Vt100 input for Posix systems. (This uses a posix file descriptor that can be registered in the event loop.) """ _fds_not_a_terminal = set() # For the error messages. Only display "Input # is not a terminal" once per file descriptor. def __init__(self, stdin): # Test whether the given input object has a file descriptor. # (Idle reports stdin to be a TTY, but fileno() is not implemented.) try: # This should not raise, but can return 0. stdin.fileno() except io.UnsupportedOperation: if 'idlelib.run' in sys.modules: raise io.UnsupportedOperation( 'Stdin is not a terminal. Running from Idle is not supported.') else: raise io.UnsupportedOperation('Stdin is not a terminal.') # Even when we have a file descriptor, it doesn't mean it's a TTY. # Normally, this requires a real TTY device, but people instantiate # this class often during unit tests as well. They use for instance # pexpect to pipe data into an application. For convenience, we print # an error message and go on. isatty = stdin.isatty() fd = stdin.fileno() if not isatty and fd not in Vt100Input._fds_not_a_terminal: msg = 'Warning: Input is not to a terminal (fd=%r).\n' sys.stderr.write(msg % fd) Vt100Input._fds_not_a_terminal.add(fd) # self.stdin = stdin # Create a backup of the fileno(). We want this to work even if the # underlying file is closed, so that `typeahead_hash()` keeps working. self._fileno = stdin.fileno() self._buffer = [] # Buffer to collect the Key objects. self.stdin_reader = PosixStdinReader(self._fileno) self.vt100_parser = Vt100Parser( lambda key: self._buffer.append(key)) @property def responds_to_cpr(self): # When the input is a tty, we assume that CPR is supported. # It's not when the input is piped from Pexpect. if os.environ.get('PROMPT_TOOLKIT_NO_CPR', '') == '1': return False return self.stdin.isatty() def attach(self, input_ready_callback): """ Return a context manager that makes this input active in the current event loop. """ assert callable(input_ready_callback) return _attached_input(self, input_ready_callback) def detach(self): """ Return a context manager that makes sure that this input is not active in the current event loop. """ return _detached_input(self) def read_keys(self): " Read list of KeyPress. " # Read text from stdin. data = self.stdin_reader.read() # Pass it through our vt100 parser. self.vt100_parser.feed(data) # Return result. result = self._buffer self._buffer = [] return result def flush_keys(self): """ Flush pending keys and return them. (Used for flushing the 'escape' key.) """ # Flush all pending keys. (This is most important to flush the vt100 # 'Escape' key early when nothing else follows.) self.vt100_parser.flush() # Return result. result = self._buffer self._buffer = [] return result @property def closed(self): return self.stdin_reader.closed def raw_mode(self): return raw_mode(self.stdin.fileno()) def cooked_mode(self): return cooked_mode(self.stdin.fileno()) def fileno(self): return self.stdin.fileno() def typeahead_hash(self): return 'fd-%s' % (self._fileno, ) _current_callbacks = {} # (loop, fd) -> current callback @contextlib.contextmanager def _attached_input(input, callback): """ Context manager that makes this input active in the current event loop. :param input: :class:`~prompt_toolkit.input.Input` object. :param callback: Called when the input is ready to read. """ loop = get_event_loop() fd = input.fileno() previous = _current_callbacks.get((loop, fd)) loop.add_reader(fd, callback) _current_callbacks[loop, fd] = callback try: yield finally: loop.remove_reader(fd) if previous: loop.add_reader(fd, previous) _current_callbacks[loop, fd] = previous else: del _current_callbacks[loop, fd] @contextlib.contextmanager def _detached_input(input): loop = get_event_loop() fd = input.fileno() previous = _current_callbacks.get((loop, fd)) if previous: loop.remove_reader(fd) _current_callbacks[loop, fd] = None try: yield finally: if previous: loop.add_reader(fd, previous) _current_callbacks[loop, fd] = previous class raw_mode(object): """ :: with raw_mode(stdin): ''' the pseudo-terminal stdin is now used in raw mode ''' We ignore errors when executing `tcgetattr` fails. """ # There are several reasons for ignoring errors: # 1. To avoid the "Inappropriate ioctl for device" crash if somebody would # execute this code (In a Python REPL, for instance): # # import os; f = open(os.devnull); os.dup2(f.fileno(), 0) # # The result is that the eventloop will stop correctly, because it has # to logic to quit when stdin is closed. However, we should not fail at # this point. See: # https://github.com/jonathanslenders/python-prompt-toolkit/pull/393 # https://github.com/jonathanslenders/python-prompt-toolkit/issues/392 # 2. Related, when stdin is an SSH pipe, and no full terminal was allocated. # See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/165 def __init__(self, fileno): self.fileno = fileno try: self.attrs_before = termios.tcgetattr(fileno) except termios.error: # Ignore attribute errors. self.attrs_before = None def __enter__(self): # NOTE: On os X systems, using pty.setraw() fails. Therefor we are using this: try: newattr = termios.tcgetattr(self.fileno) except termios.error: pass else: newattr[tty.LFLAG] = self._patch_lflag(newattr[tty.LFLAG]) newattr[tty.IFLAG] = self._patch_iflag(newattr[tty.IFLAG]) # VMIN defines the number of characters read at a time in # non-canonical mode. It seems to default to 1 on Linux, but on # Solaris and derived operating systems it defaults to 4. (This is # because the VMIN slot is the same as the VEOF slot, which # defaults to ASCII EOT = Ctrl-D = 4.) newattr[tty.CC][termios.VMIN] = 1 termios.tcsetattr(self.fileno, termios.TCSANOW, newattr) # Put the terminal in cursor mode. (Instead of application mode.) os.write(self.fileno, b'\x1b[?1l') @classmethod def _patch_lflag(cls, attrs): return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) @classmethod def _patch_iflag(cls, attrs): return attrs & ~( # Disable XON/XOFF flow control on output and input. # (Don't capture Ctrl-S and Ctrl-Q.) # Like executing: "stty -ixon." termios.IXON | termios.IXOFF | # Don't translate carriage return into newline on input. termios.ICRNL | termios.INLCR | termios.IGNCR ) def __exit__(self, *a, **kw): if self.attrs_before is not None: try: termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) except termios.error: pass # # Put the terminal in application mode. # self._stdout.write('\x1b[?1h') class cooked_mode(raw_mode): """ The opposite of ``raw_mode``, used when we need cooked mode inside a `raw_mode` block. Used in `Application.run_in_terminal`.:: with cooked_mode(stdin): ''' the pseudo-terminal stdin is now used in cooked mode. ''' """ @classmethod def _patch_lflag(cls, attrs): return attrs | (termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) @classmethod def _patch_iflag(cls, attrs): # Turn the ICRNL flag back on. (Without this, calling `input()` in # run_in_terminal doesn't work and displays ^M instead. Ptpython # evaluates commands using `run_in_terminal`, so it's important that # they translate ^M back into ^J.) return attrs | termios.ICRNL prompt_toolkit-2.0.10/prompt_toolkit/input/vt100_parser.py0000644000175100017510000001725113545407204025347 0ustar jonathanjonathan00000000000000""" Parser for VT100 input stream. """ from __future__ import unicode_literals import re import six from six.moves import range from ..key_binding.key_processor import KeyPress from ..keys import Keys from .ansi_escape_sequences import ANSI_SEQUENCES __all__ = [ 'Vt100Parser', ] # Regex matching any CPR response # (Note that we use '\Z' instead of '$', because '$' could include a trailing # newline.) _cpr_response_re = re.compile('^' + re.escape('\x1b[') + r'\d+;\d+R\Z') # Mouse events: # Typical: "Esc[MaB*" Urxvt: "Esc[96;14;13M" and for Xterm SGR: "Esc[<64;85;12M" _mouse_event_re = re.compile('^' + re.escape('\x1b[') + r'(= 1 and text_count > 1 def _event_to_key_presses(self, ev): """ For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. """ assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown result = None u_char = ev.uChar.UnicodeChar ascii_char = u_char.encode('utf-8') # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be latin-1 # encoded. See also: # https://github.com/ipython/ipython/issues/10004 # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389 if u_char == '\x00': if ev.VirtualKeyCode in self.keycodes: result = KeyPress(self.keycodes[ev.VirtualKeyCode], '') else: if ascii_char in self.mappings: if self.mappings[ascii_char] == Keys.ControlJ: u_char = '\n' # Windows sends \n, turn into \r for unix compatibility. result = KeyPress(self.mappings[ascii_char], u_char) else: result = KeyPress(u_char, u_char) # Correctly handle Control-Arrow keys. if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result: if result.key == Keys.Left: result.key = Keys.ControlLeft if result.key == Keys.Right: result.key = Keys.ControlRight if result.key == Keys.Up: result.key = Keys.ControlUp if result.key == Keys.Down: result.key = Keys.ControlDown # Turn 'Tab' into 'BackTab' when shift was pressed. if ev.ControlKeyState & self.SHIFT_PRESSED and result: if result.key == Keys.Tab: result.key = Keys.BackTab # Turn 'Space' into 'ControlSpace' when control was pressed. if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and result.data == ' ': result = KeyPress(Keys.ControlSpace, ' ') # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot # detect this combination. But it's really practical on Windows.) if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and \ result.key == Keys.ControlJ: return [KeyPress(Keys.Escape, ''), result] # Return result. If alt was pressed, prefix the result with an # 'Escape' key, just like unix VT100 terminals do. # NOTE: Only replace the left alt with escape. The right alt key often # acts as altgr and is used in many non US keyboard layouts for # typing some special characters, like a backslash. We don't want # all backslashes to be prefixed with escape. (Esc-\ has a # meaning in E-macs, for instance.) if result: meta_pressed = ev.ControlKeyState & self.LEFT_ALT_PRESSED if meta_pressed: return [KeyPress(Keys.Escape, ''), result] else: return [result] else: return [] def _handle_mouse(self, ev): """ Handle mouse events. Return a list of KeyPress instances. """ FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 result = [] # Check event type. if ev.ButtonState == FROM_LEFT_1ST_BUTTON_PRESSED: # On a key press, generate both the mouse down and up event. for event_type in [MouseEventType.MOUSE_DOWN, MouseEventType.MOUSE_UP]: data = ';'.join([ event_type, str(ev.MousePosition.X), str(ev.MousePosition.Y) ]) result.append(KeyPress(Keys.WindowsMouseEvent, data)) return result _current_callbacks = {} # loop -> callback @contextmanager def attach_win32_input(input, callback): """ Context manager that makes this input active in the current event loop. :param input: :class:`~prompt_toolkit.input.Input` object. :param input_ready_callback: Called when the input is ready to read. """ assert isinstance(input, Input) assert callable(callback) loop = get_event_loop() previous_callback = _current_callbacks.get(loop) # Add reader. loop.add_win32_handle(input.handle, callback) _current_callbacks[loop] = callback try: yield finally: loop.remove_win32_handle(input.handle) if previous_callback: loop.add_win32_handle(input.handle, previous_callback) _current_callbacks[loop] = previous_callback else: del _current_callbacks[loop] @contextmanager def detach_win32_input(input): assert isinstance(input, Input) loop = get_event_loop() previous = _current_callbacks.get(loop) if previous: loop.remove_win32_handle(input.handle) _current_callbacks[loop] = None try: yield finally: if previous: loop.add_win32_handle(input.handle, previous) _current_callbacks[loop] = previous class raw_mode(object): """ :: with raw_mode(stdin): ''' the windows terminal is now in 'raw' mode. ''' The ``fileno`` attribute is ignored. This is to be compatible with the `raw_input` method of `.vt100_input`. """ def __init__(self, fileno=None): self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) def __enter__(self): # Remember original mode. original_mode = DWORD() windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode)) self.original_mode = original_mode self._patch() def _patch(self): # Set raw ENABLE_ECHO_INPUT = 0x0004 ENABLE_LINE_INPUT = 0x0002 ENABLE_PROCESSED_INPUT = 0x0001 windll.kernel32.SetConsoleMode( self.handle, self.original_mode.value & ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) def __exit__(self, *a, **kw): # Restore original mode windll.kernel32.SetConsoleMode(self.handle, self.original_mode) class cooked_mode(raw_mode): """ :: with cooked_mode(stdin): ''' The pseudo-terminal stdin is now used in cooked mode. ''' """ def _patch(self): # Set cooked. ENABLE_ECHO_INPUT = 0x0004 ENABLE_LINE_INPUT = 0x0002 ENABLE_PROCESSED_INPUT = 0x0001 windll.kernel32.SetConsoleMode( self.handle, self.original_mode.value | (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) prompt_toolkit-2.0.10/prompt_toolkit/input/win32_pipe.py0000644000175100017510000000737613545407204025107 0ustar jonathanjonathan00000000000000from ctypes import windll from prompt_toolkit.eventloop.win32 import create_win32_event from ..utils import DummyContext from .base import Input from .vt100_parser import Vt100Parser from .win32 import attach_win32_input, detach_win32_input __all__ = [ 'Win32PipeInput' ] class Win32PipeInput(Input): """ This is an input pipe that works on Windows. Text or bytes can be feed into the pipe, and key strokes can be read from the pipe. This is useful if we want to send the input programmatically into the application. Mostly useful for unit testing. Notice that even though it's Windows, we use vt100 escape sequences over the pipe. Usage:: input = Win32PipeInput() input.send_text('inputdata') """ _id = 0 def __init__(self): # Event (handle) for registering this input in the event loop. # This event is set when there is data available to read from the pipe. # Note: We use this approach instead of using a regular pipe, like # returned from `os.pipe()`, because making such a regular pipe # non-blocking is tricky and this works really well. self._event = create_win32_event() self._closed = False # Parser for incoming keys. self._buffer = [] # Buffer to collect the Key objects. self.vt100_parser = Vt100Parser( lambda key: self._buffer.append(key)) # Identifier for every PipeInput for the hash. self.__class__._id += 1 self._id = self.__class__._id @property def closed(self): return self._closed def fileno(self): """ The windows pipe doesn't depend on the file handle. """ raise NotImplementedError @property def handle(self): " The handle used for registering this pipe in the event loop. " return self._event def attach(self, input_ready_callback): """ Return a context manager that makes this input active in the current event loop. """ assert callable(input_ready_callback) return attach_win32_input(self, input_ready_callback) def detach(self): """ Return a context manager that makes sure that this input is not active in the current event loop. """ return detach_win32_input(self) def read_keys(self): " Read list of KeyPress. " # Return result. result = self._buffer self._buffer = [] # Reset event. windll.kernel32.ResetEvent(self._event) return result def flush_keys(self): """ Flush pending keys and return them. (Used for flushing the 'escape' key.) """ # Flush all pending keys. (This is most important to flush the vt100 # 'Escape' key early when nothing else follows.) self.vt100_parser.flush() # Return result. result = self._buffer self._buffer = [] return result @property def responds_to_cpr(self): return False def send_bytes(self, data): " Send bytes to the input. " self.send_text(data.decode('utf-8', 'ignore')) def send_text(self, text): " Send text to the input. " # Pass it through our vt100 parser. self.vt100_parser.feed(text) # Set event. windll.kernel32.SetEvent(self._event) def raw_mode(self): return DummyContext() def cooked_mode(self): return DummyContext() def close(self): " Close pipe handles. " windll.kernel32.CloseHandle(self._event) self._closed = True def typeahead_hash(self): """ This needs to be unique for every `PipeInput`. """ return 'pipe-input-%s' % (self._id, ) prompt_toolkit-2.0.10/prompt_toolkit/key_binding/0000755000175100017510000000000013545410361023662 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/key_binding/__init__.py0000644000175100017510000000050413545407204025774 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .key_bindings import ( ConditionalKeyBindings, DynamicKeyBindings, KeyBindings, KeyBindingsBase, merge_key_bindings, ) __all__ = [ 'KeyBindingsBase', 'KeyBindings', 'ConditionalKeyBindings', 'merge_key_bindings', 'DynamicKeyBindings', ] prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/0000755000175100017510000000000013545410361025457 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/__init__.py0000644000175100017510000000000013545407022027556 0ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/auto_suggest.py0000644000175100017510000000306613545407204030551 0ustar jonathanjonathan00000000000000""" Key bindings for auto suggestion (for fish-style auto suggestion). """ from __future__ import unicode_literals import re from prompt_toolkit.application.current import get_app from prompt_toolkit.filters import Condition, emacs_mode from prompt_toolkit.key_binding.key_bindings import KeyBindings __all__ = [ 'load_auto_suggest_bindings', ] def load_auto_suggest_bindings(): """ Key bindings for accepting auto suggestion text. (This has to come after the Vi bindings, because they also have an implementation for the "right arrow", but we really want the suggestion binding when a suggestion is available.) """ key_bindings = KeyBindings() handle = key_bindings.add @Condition def suggestion_available(): app = get_app() return (app.current_buffer.suggestion is not None and app.current_buffer.document.is_cursor_at_the_end) @handle('c-f', filter=suggestion_available) @handle('c-e', filter=suggestion_available) @handle('right', filter=suggestion_available) def _(event): " Accept suggestion. " b = event.current_buffer suggestion = b.suggestion if suggestion: b.insert_text(suggestion.text) @handle('escape', 'f', filter=suggestion_available & emacs_mode) def _(event): " Fill partial suggestion. " b = event.current_buffer suggestion = b.suggestion if suggestion: t = re.split(r'(\S+\s+)', suggestion.text) b.insert_text(next(x for x in t if x)) return key_bindings prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/basic.py0000644000175100017510000001461513545407204027123 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.application.current import get_app from prompt_toolkit.filters import ( Condition, emacs_insert_mode, has_selection, in_paste_mode, is_multiline, vi_insert_mode, ) from prompt_toolkit.key_binding.key_processor import KeyPress from prompt_toolkit.keys import Keys from ..key_bindings import KeyBindings from .named_commands import get_by_name __all__ = [ 'load_basic_bindings', ] def if_no_repeat(event): """ Callable that returns True when the previous event was delivered to another handler. """ return not event.is_repeat def load_basic_bindings(): key_bindings = KeyBindings() insert_mode = vi_insert_mode | emacs_insert_mode handle = key_bindings.add @handle('c-a') @handle('c-b') @handle('c-c') @handle('c-d') @handle('c-e') @handle('c-f') @handle('c-g') @handle('c-h') @handle('c-i') @handle('c-j') @handle('c-k') @handle('c-l') @handle('c-m') @handle('c-n') @handle('c-o') @handle('c-p') @handle('c-q') @handle('c-r') @handle('c-s') @handle('c-t') @handle('c-u') @handle('c-v') @handle('c-w') @handle('c-x') @handle('c-y') @handle('c-z') @handle('f1') @handle('f2') @handle('f3') @handle('f4') @handle('f5') @handle('f6') @handle('f7') @handle('f8') @handle('f9') @handle('f10') @handle('f11') @handle('f12') @handle('f13') @handle('f14') @handle('f15') @handle('f16') @handle('f17') @handle('f18') @handle('f19') @handle('f20') @handle('c-@') # Also c-space. @handle('c-\\') @handle('c-]') @handle('c-^') @handle('c-_') @handle('backspace') @handle('up') @handle('down') @handle('right') @handle('left') @handle('s-up') @handle('s-down') @handle('s-right') @handle('s-left') @handle('home') @handle('end') @handle('delete') @handle('s-delete') @handle('c-delete') @handle('pageup') @handle('pagedown') @handle('s-tab') @handle('tab') @handle('c-left') @handle('c-right') @handle('c-up') @handle('c-down') @handle('insert') @handle(Keys.Ignore) def _(event): """ First, for any of these keys, Don't do anything by default. Also don't catch them in the 'Any' handler which will insert them as data. If people want to insert these characters as a literal, they can always do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi mode.) """ pass # Readline-style bindings. handle('home')(get_by_name('beginning-of-line')) handle('end')(get_by_name('end-of-line')) handle('left')(get_by_name('backward-char')) handle('right')(get_by_name('forward-char')) handle('c-up')(get_by_name('previous-history')) handle('c-down')(get_by_name('next-history')) handle('c-l')(get_by_name('clear-screen')) handle('c-k', filter=insert_mode)(get_by_name('kill-line')) handle('c-u', filter=insert_mode)(get_by_name('unix-line-discard')) handle('backspace', filter=insert_mode, save_before=if_no_repeat)( get_by_name('backward-delete-char')) handle('delete', filter=insert_mode, save_before=if_no_repeat)( get_by_name('delete-char')) handle('c-delete', filter=insert_mode, save_before=if_no_repeat)( get_by_name('delete-char')) handle(Keys.Any, filter=insert_mode, save_before=if_no_repeat)( get_by_name('self-insert')) handle('c-t', filter=insert_mode)(get_by_name('transpose-chars')) handle('c-i', filter=insert_mode)(get_by_name('menu-complete')) handle('s-tab', filter=insert_mode)(get_by_name('menu-complete-backward')) # Control-W should delete, using whitespace as separator, while M-Del # should delete using [^a-zA-Z0-9] as a boundary. handle('c-w', filter=insert_mode)(get_by_name('unix-word-rubout')) handle('pageup', filter= ~has_selection)(get_by_name('previous-history')) handle('pagedown', filter= ~has_selection)(get_by_name('next-history')) # CTRL keys. text_before_cursor = Condition(lambda: get_app().current_buffer.text) handle('c-d', filter=text_before_cursor & insert_mode)(get_by_name('delete-char')) @handle('enter', filter=insert_mode & is_multiline) def _(event): " Newline (in case of multiline input. " event.current_buffer.newline(copy_margin=not in_paste_mode()) @handle('c-j') def _(event): r""" By default, handle \n as if it were a \r (enter). (It appears that some terminals send \n instead of \r when pressing enter. - at least the Linux subsystem for Windows.) """ event.key_processor.feed( KeyPress(Keys.ControlM, '\r'), first=True) # Delete the word before the cursor. @handle('up') def _(event): event.current_buffer.auto_up(count=event.arg) @handle('down') def _(event): event.current_buffer.auto_down(count=event.arg) @handle('delete', filter=has_selection) def _(event): data = event.current_buffer.cut_selection() event.app.clipboard.set_data(data) # Global bindings. @handle('c-z') def _(event): """ By default, control-Z should literally insert Ctrl-Z. (Ansi Ctrl-Z, code 26 in MSDOS means End-Of-File. In a Python REPL for instance, it's possible to type Control-Z followed by enter to quit.) When the system bindings are loaded and suspend-to-background is supported, that will override this binding. """ event.current_buffer.insert_text(event.data) @handle(Keys.BracketedPaste) def _(event): " Pasting from clipboard. " data = event.data # Be sure to use \n as line ending. # Some terminals (Like iTerm2) seem to paste \r\n line endings in a # bracketed paste. See: https://github.com/ipython/ipython/issues/9737 data = data.replace('\r\n', '\n') data = data.replace('\r', '\n') event.current_buffer.insert_text(data) @handle(Keys.Any, filter=Condition(lambda: get_app().quoted_insert), eager=True) def _(event): """ Handle quoted insert. """ event.current_buffer.insert_text(event.data, overwrite=False) event.app.quoted_insert = False return key_bindings prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/completion.py0000644000175100017510000001421713545407204030211 0ustar jonathanjonathan00000000000000""" Key binding handlers for displaying completions. """ from __future__ import unicode_literals import math from prompt_toolkit.application.run_in_terminal import ( run_coroutine_in_terminal, ) from prompt_toolkit.completion import CompleteEvent, get_common_complete_suffix from prompt_toolkit.key_binding.key_bindings import KeyBindings from prompt_toolkit.keys import Keys from prompt_toolkit.utils import get_cwidth __all__ = [ 'generate_completions', 'display_completions_like_readline', ] def generate_completions(event): r""" Tab-completion: where the first tab completes the common suffix and the second tab lists all the completions. """ b = event.current_buffer # When already navigating through completions, select the next one. if b.complete_state: b.complete_next() else: b.start_completion(insert_common_part=True) def display_completions_like_readline(event): """ Key binding handler for readline-style tab completion. This is meant to be as similar as possible to the way how readline displays completions. Generate the completions immediately (blocking) and display them above the prompt in columns. Usage:: # Call this handler when 'Tab' has been pressed. key_bindings.add(Keys.ControlI)(display_completions_like_readline) """ # Request completions. b = event.current_buffer if b.completer is None: return complete_event = CompleteEvent(completion_requested=True) completions = list(b.completer.get_completions(b.document, complete_event)) # Calculate the common suffix. common_suffix = get_common_complete_suffix(b.document, completions) # One completion: insert it. if len(completions) == 1: b.delete_before_cursor(-completions[0].start_position) b.insert_text(completions[0].text) # Multiple completions with common part. elif common_suffix: b.insert_text(common_suffix) # Otherwise: display all completions. elif completions: _display_completions_like_readline(event.app, completions) def _display_completions_like_readline(app, completions): """ Display the list of completions in columns above the prompt. This will ask for a confirmation if there are too many completions to fit on a single page and provide a paginator to walk through them. """ from prompt_toolkit.shortcuts.prompt import create_confirm_session from prompt_toolkit.formatted_text import to_formatted_text assert isinstance(completions, list) # Get terminal dimensions. term_size = app.output.get_size() term_width = term_size.columns term_height = term_size.rows # Calculate amount of required columns/rows for displaying the # completions. (Keep in mind that completions are displayed # alphabetically column-wise.) max_compl_width = min(term_width, max(get_cwidth(c.display_text) for c in completions) + 1) column_count = max(1, term_width // max_compl_width) completions_per_page = column_count * (term_height - 1) page_count = int(math.ceil(len(completions) / float(completions_per_page))) # Note: math.ceil can return float on Python2. def display(page): # Display completions. page_completions = completions[page * completions_per_page: (page + 1) * completions_per_page] page_row_count = int(math.ceil(len(page_completions) / float(column_count))) page_columns = [page_completions[i * page_row_count:(i + 1) * page_row_count] for i in range(column_count)] result = [] # FormattedText list: (style,text) tuples. for r in range(page_row_count): for c in range(column_count): try: completion = page_columns[c][r] style = 'class:readline-like-completions.completion ' + (completion.style or '') result.extend(to_formatted_text(completion.display, style=style)) # Add padding. padding = max_compl_width - get_cwidth(completion.display_text) result.append((completion.style, ' ' * padding,)) except IndexError: pass result.append(('', '\n')) app.print_text(to_formatted_text(result, 'class:readline-like-completions')) # User interaction through an application generator function. def run_compl(): " Coroutine. " if len(completions) > completions_per_page: # Ask confirmation if it doesn't fit on the screen. confirm = yield create_confirm_session( 'Display all {} possibilities?'.format(len(completions)), ).prompt(async_=True) if confirm: # Display pages. for page in range(page_count): display(page) if page != page_count - 1: # Display --MORE-- and go to the next page. show_more = yield _create_more_session('--MORE--').prompt(async_=True) if not show_more: return else: app.output.flush() else: # Display all completions. display(0) run_coroutine_in_terminal(run_compl, render_cli_done=True) def _create_more_session(message='--MORE--'): """ Create a `PromptSession` object for displaying the "--MORE--". """ from prompt_toolkit.shortcuts import PromptSession bindings = KeyBindings() @bindings.add(' ') @bindings.add('y') @bindings.add('Y') @bindings.add(Keys.ControlJ) @bindings.add(Keys.ControlM) @bindings.add(Keys.ControlI) # Tab. def _(event): event.app.exit(result=True) @bindings.add('n') @bindings.add('N') @bindings.add('q') @bindings.add('Q') @bindings.add(Keys.ControlC) def _(event): event.app.exit(result=False) @bindings.add(Keys.Any) def _(event): " Disable inserting of text. " session = PromptSession(message, key_bindings=bindings, erase_when_done=True) return session prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/cpr.py0000644000175100017510000000124713545407204026623 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.keys import Keys from ..key_bindings import KeyBindings __all__ = [ 'load_cpr_bindings', ] def load_cpr_bindings(): key_bindings = KeyBindings() @key_bindings.add(Keys.CPRResponse, save_before=lambda e: False) def _(event): """ Handle incoming Cursor-Position-Request response. """ # The incoming data looks like u'\x1b[35;1R' # Parse row/col information. row, col = map(int, event.data[2:-1].split(';')) # Report absolute cursor position to the renderer. event.app.renderer.report_absolute_cursor_row(row) return key_bindings prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/emacs.py0000644000175100017510000003040513545407204027125 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.application.current import get_app from prompt_toolkit.buffer import SelectionType, indent, unindent from prompt_toolkit.completion import CompleteEvent from prompt_toolkit.filters import ( Condition, emacs_insert_mode, emacs_mode, has_arg, has_selection, is_multiline, is_read_only, vi_search_direction_reversed, ) from prompt_toolkit.keys import Keys from ..key_bindings import ConditionalKeyBindings, KeyBindings from .named_commands import get_by_name __all__ = [ 'load_emacs_bindings', 'load_emacs_search_bindings', ] def load_emacs_bindings(): """ Some e-macs extensions. """ # Overview of Readline emacs commands: # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf key_bindings = KeyBindings() handle = key_bindings.add insert_mode = emacs_insert_mode @handle('escape') def _(event): """ By default, ignore escape key. (If we don't put this here, and Esc is followed by a key which sequence is not handled, we'll insert an Escape character in the input stream. Something we don't want and happens to easily in emacs mode. Further, people can always use ControlQ to do a quoted insert.) """ pass handle('c-a')(get_by_name('beginning-of-line')) handle('c-b')(get_by_name('backward-char')) handle('c-delete', filter=insert_mode)(get_by_name('kill-word')) handle('c-e')(get_by_name('end-of-line')) handle('c-f')(get_by_name('forward-char')) handle('c-left')(get_by_name('backward-word')) handle('c-right')(get_by_name('forward-word')) handle('c-x', 'r', 'y', filter=insert_mode)(get_by_name('yank')) handle('c-y', filter=insert_mode)(get_by_name('yank')) handle('escape', 'b')(get_by_name('backward-word')) handle('escape', 'c', filter=insert_mode)(get_by_name('capitalize-word')) handle('escape', 'd', filter=insert_mode)(get_by_name('kill-word')) handle('escape', 'f')(get_by_name('forward-word')) handle('escape', 'l', filter=insert_mode)(get_by_name('downcase-word')) handle('escape', 'u', filter=insert_mode)(get_by_name('uppercase-word')) handle('escape', 'y', filter=insert_mode)(get_by_name('yank-pop')) handle('escape', 'backspace', filter=insert_mode)(get_by_name('backward-kill-word')) handle('escape', '\\', filter=insert_mode)(get_by_name('delete-horizontal-space')) handle('c-_', save_before=(lambda e: False), filter=insert_mode)( get_by_name('undo')) handle('c-x', 'c-u', save_before=(lambda e: False), filter=insert_mode)( get_by_name('undo')) handle('escape', '<', filter= ~has_selection)(get_by_name('beginning-of-history')) handle('escape', '>', filter= ~has_selection)(get_by_name('end-of-history')) handle('escape', '.', filter=insert_mode)(get_by_name('yank-last-arg')) handle('escape', '_', filter=insert_mode)(get_by_name('yank-last-arg')) handle('escape', 'c-y', filter=insert_mode)(get_by_name('yank-nth-arg')) handle('escape', '#', filter=insert_mode)(get_by_name('insert-comment')) handle('c-o')(get_by_name('operate-and-get-next')) # ControlQ does a quoted insert. Not that for vt100 terminals, you have to # disable flow control by running ``stty -ixon``, otherwise Ctrl-Q and # Ctrl-S are captured by the terminal. handle('c-q', filter= ~has_selection)(get_by_name('quoted-insert')) handle('c-x', '(')(get_by_name('start-kbd-macro')) handle('c-x', ')')(get_by_name('end-kbd-macro')) handle('c-x', 'e')(get_by_name('call-last-kbd-macro')) @handle('c-n') def _(event): " Next line. " event.current_buffer.auto_down() @handle('c-p') def _(event): " Previous line. " event.current_buffer.auto_up(count=event.arg) def handle_digit(c): """ Handle input of arguments. The first number needs to be preceded by escape. """ @handle(c, filter=has_arg) @handle('escape', c) def _(event): event.append_to_arg_count(c) for c in '0123456789': handle_digit(c) @handle('escape', '-', filter=~has_arg) def _(event): """ """ if event._arg is None: event.append_to_arg_count('-') @handle('-', filter=Condition(lambda: get_app().key_processor.arg == '-')) def _(event): """ When '-' is typed again, after exactly '-' has been given as an argument, ignore this. """ event.app.key_processor.arg = '-' @Condition def is_returnable(): return get_app().current_buffer.is_returnable # Meta + Enter: always accept input. handle('escape', 'enter', filter=insert_mode & is_returnable)( get_by_name('accept-line')) # Enter: accept input in single line mode. handle('enter', filter=insert_mode & is_returnable & ~is_multiline)( get_by_name('accept-line')) def character_search(buff, char, count): if count < 0: match = buff.document.find_backwards(char, in_current_line=True, count=-count) else: match = buff.document.find(char, in_current_line=True, count=count) if match is not None: buff.cursor_position += match @handle('c-]', Keys.Any) def _(event): " When Ctl-] + a character is pressed. go to that character. " # Also named 'character-search' character_search(event.current_buffer, event.data, event.arg) @handle('escape', 'c-]', Keys.Any) def _(event): " Like Ctl-], but backwards. " # Also named 'character-search-backward' character_search(event.current_buffer, event.data, -event.arg) @handle('escape', 'a') def _(event): " Previous sentence. " # TODO: @handle('escape', 'e') def _(event): " Move to end of sentence. " # TODO: @handle('escape', 't', filter=insert_mode) def _(event): """ Swap the last two words before the cursor. """ # TODO @handle('escape', '*', filter=insert_mode) def _(event): """ `meta-*`: Insert all possible completions of the preceding text. """ buff = event.current_buffer # List all completions. complete_event = CompleteEvent(text_inserted=False, completion_requested=True) completions = list(buff.completer.get_completions(buff.document, complete_event)) # Insert them. text_to_insert = ' '.join(c.text for c in completions) buff.insert_text(text_to_insert) @handle('c-x', 'c-x') def _(event): """ Move cursor back and forth between the start and end of the current line. """ buffer = event.current_buffer if buffer.document.is_cursor_at_the_end_of_line: buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=False) else: buffer.cursor_position += buffer.document.get_end_of_line_position() @handle('c-@') # Control-space or Control-@ def _(event): """ Start of the selection (if the current buffer is not empty). """ # Take the current cursor position as the start of this selection. buff = event.current_buffer if buff.text: buff.start_selection(selection_type=SelectionType.CHARACTERS) @handle('c-g', filter= ~has_selection) def _(event): """ Control + G: Cancel completion menu and validation state. """ event.current_buffer.complete_state = None event.current_buffer.validation_error = None @handle('c-g', filter=has_selection) def _(event): """ Cancel selection. """ event.current_buffer.exit_selection() @handle('c-w', filter=has_selection) @handle('c-x', 'r', 'k', filter=has_selection) def _(event): """ Cut selected text. """ data = event.current_buffer.cut_selection() event.app.clipboard.set_data(data) @handle('escape', 'w', filter=has_selection) def _(event): """ Copy selected text. """ data = event.current_buffer.copy_selection() event.app.clipboard.set_data(data) @handle('escape', 'left') def _(event): """ Cursor to start of previous word. """ buffer = event.current_buffer buffer.cursor_position += buffer.document.find_previous_word_beginning(count=event.arg) or 0 @handle('escape', 'right') def _(event): """ Cursor to start of next word. """ buffer = event.current_buffer buffer.cursor_position += buffer.document.find_next_word_beginning(count=event.arg) or \ buffer.document.get_end_of_document_position() @handle('escape', '/', filter=insert_mode) def _(event): """ M-/: Complete. """ b = event.current_buffer if b.complete_state: b.complete_next() else: b.start_completion(select_first=True) @handle('c-c', '>', filter=has_selection) def _(event): """ Indent selected text. """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) from_, to = buffer.document.selection_range() from_, _ = buffer.document.translate_index_to_position(from_) to, _ = buffer.document.translate_index_to_position(to) indent(buffer, from_, to + 1, count=event.arg) @handle('c-c', '<', filter=has_selection) def _(event): """ Unindent selected text. """ buffer = event.current_buffer from_, to = buffer.document.selection_range() from_, _ = buffer.document.translate_index_to_position(from_) to, _ = buffer.document.translate_index_to_position(to) unindent(buffer, from_, to + 1, count=event.arg) return ConditionalKeyBindings(key_bindings, emacs_mode) def load_emacs_search_bindings(): key_bindings = KeyBindings() handle = key_bindings.add from . import search # NOTE: We don't bind 'Escape' to 'abort_search'. The reason is that we # want Alt+Enter to accept input directly in incremental search mode. # Instead, we have double escape. handle('c-r')(search.start_reverse_incremental_search) handle('c-s')(search.start_forward_incremental_search) handle('c-c')(search.abort_search) handle('c-g')(search.abort_search) handle('c-r')(search.reverse_incremental_search) handle('c-s')(search.forward_incremental_search) handle('up')(search.reverse_incremental_search) handle('down')(search.forward_incremental_search) handle('enter')(search.accept_search) # Handling of escape. handle('escape', eager=True)(search.accept_search) # Like Readline, it's more natural to accept the search when escape has # been pressed, however instead the following two bindings could be used # instead. # #handle('escape', 'escape', eager=True)(search.abort_search) # #handle('escape', 'enter', eager=True)(search.accept_search_and_accept_input) # If Read-only: also include the following key bindings: # '/' and '?' key bindings for searching, just like Vi mode. handle('?', filter=is_read_only & ~vi_search_direction_reversed)(search.start_reverse_incremental_search) handle('/', filter=is_read_only & ~vi_search_direction_reversed)(search.start_forward_incremental_search) handle('?', filter=is_read_only & vi_search_direction_reversed)(search.start_forward_incremental_search) handle('/', filter=is_read_only & vi_search_direction_reversed)(search.start_reverse_incremental_search) @handle('n', filter=is_read_only) def _(event): " Jump to next match. " event.current_buffer.apply_search( event.app.current_search_state, include_current_position=False, count=event.arg) @handle('N', filter=is_read_only) def _(event): " Jump to previous match. " event.current_buffer.apply_search( ~event.app.current_search_state, include_current_position=False, count=event.arg) return ConditionalKeyBindings(key_bindings, emacs_mode) prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/focus.py0000644000175100017510000000062313545407204027153 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = [ 'focus_next', 'focus_previous', ] def focus_next(event): """ Focus the next visible Window. (Often bound to the `Tab` key.) """ event.app.layout.focus_next() def focus_previous(event): """ Focus the previous visible Window. (Often bound to the `BackTab` key.) """ event.app.layout.focus_previous() prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/mouse.py0000644000175100017510000001073513545407204027171 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.key_binding.key_processor import KeyPress from prompt_toolkit.keys import Keys from prompt_toolkit.layout.screen import Point from prompt_toolkit.mouse_events import MouseEvent, MouseEventType from prompt_toolkit.renderer import HeightIsUnknownError from prompt_toolkit.utils import is_windows from ..key_bindings import KeyBindings __all__ = [ 'load_mouse_bindings', ] def load_mouse_bindings(): """ Key bindings, required for mouse support. (Mouse events enter through the key binding system.) """ key_bindings = KeyBindings() @key_bindings.add(Keys.Vt100MouseEvent) def _(event): """ Handling of incoming mouse event. """ # TypicaL: "eSC[MaB*" # Urxvt: "Esc[96;14;13M" # Xterm SGR: "Esc[<64;85;12M" # Parse incoming packet. if event.data[2] == 'M': # Typical. mouse_event, x, y = map(ord, event.data[3:]) mouse_event = { 32: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, }.get(mouse_event) # Handle situations where `PosixStdinReader` used surrogateescapes. if x >= 0xdc00: x -= 0xdc00 if y >= 0xdc00: y -= 0xdc00 x -= 32 y -= 32 else: # Urxvt and Xterm SGR. # When the '<' is not present, we are not using the Xterm SGR mode, # but Urxvt instead. data = event.data[2:] if data[:1] == '<': sgr = True data = data[1:] else: sgr = False # Extract coordinates. mouse_event, x, y = map(int, data[:-1].split(';')) m = data[-1] # Parse event type. if sgr: mouse_event = { (0, 'M'): MouseEventType.MOUSE_DOWN, (0, 'm'): MouseEventType.MOUSE_UP, (64, 'M'): MouseEventType.SCROLL_UP, (65, 'M'): MouseEventType.SCROLL_DOWN, }.get((mouse_event, m)) else: mouse_event = { 32: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, }.get(mouse_event) x -= 1 y -= 1 # Only handle mouse events when we know the window height. if event.app.renderer.height_is_known and mouse_event is not None: # Take region above the layout into account. The reported # coordinates are absolute to the visible part of the terminal. try: y -= event.app.renderer.rows_above_layout except HeightIsUnknownError: return # Call the mouse handler from the renderer. handler = event.app.renderer.mouse_handlers.mouse_handlers[x, y] handler(MouseEvent(position=Point(x=x, y=y), event_type=mouse_event)) @key_bindings.add(Keys.ScrollUp) def _(event): " Scroll up event without cursor position. " # We don't receive a cursor position, so we don't know which window to # scroll. Just send an 'up' key press instead. event.key_processor.feed(KeyPress(Keys.Up), first=True) @key_bindings.add(Keys.ScrollDown) def _(event): " Scroll down event without cursor position. " event.key_processor.feed(KeyPress(Keys.Down), first=True) @key_bindings.add(Keys.WindowsMouseEvent) def _(event): """ Handling of mouse events for Windows. """ assert is_windows() # This key binding should only exist for Windows. # Parse data. event_type, x, y = event.data.split(';') x = int(x) y = int(y) # Make coordinates absolute to the visible part of the terminal. screen_buffer_info = event.app.renderer.output.get_win32_screen_buffer_info() rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.app.renderer._cursor_pos.y y -= rows_above_cursor # Call the mouse event handler. handler = event.app.renderer.mouse_handlers.mouse_handlers[x, y] handler(MouseEvent(position=Point(x=x, y=y), event_type=event_type)) return key_bindings prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/named_commands.py0000644000175100017510000004042713545407204031007 0ustar jonathanjonathan00000000000000""" Key bindings which are also known by GNU Readline by the given names. See: http://www.delorie.com/gnu/docs/readline/rlman_13.html """ from __future__ import unicode_literals import six from six.moves import range from prompt_toolkit.document import Document from prompt_toolkit.enums import EditingMode from prompt_toolkit.key_binding.key_bindings import key_binding from prompt_toolkit.key_binding.key_processor import KeyPress from prompt_toolkit.keys import Keys from prompt_toolkit.search import SearchDirection from prompt_toolkit.selection import PasteMode from .completion import display_completions_like_readline, generate_completions __all__ = [ 'get_by_name', ] # Registry that maps the Readline command names to their handlers. _readline_commands = {} def register(name): """ Store handler in the `_readline_commands` dictionary. """ assert isinstance(name, six.text_type) def decorator(handler): " `handler` is a callable or _Binding. " _readline_commands[name] = handler return handler return decorator def get_by_name(name): """ Return the handler for the (Readline) command with the given name. """ try: return _readline_commands[name] except KeyError: raise KeyError('Unknown Readline command: %r' % name) # # Commands for moving # See: http://www.delorie.com/gnu/docs/readline/rlman_14.html # @register('beginning-of-line') def beginning_of_line(event): " Move to the start of the current line. " buff = event.current_buffer buff.cursor_position += buff.document.get_start_of_line_position(after_whitespace=False) @register('end-of-line') def end_of_line(event): " Move to the end of the line. " buff = event.current_buffer buff.cursor_position += buff.document.get_end_of_line_position() @register('forward-char') def forward_char(event): " Move forward a character. " buff = event.current_buffer buff.cursor_position += buff.document.get_cursor_right_position(count=event.arg) @register('backward-char') def backward_char(event): " Move back a character. " buff = event.current_buffer buff.cursor_position += buff.document.get_cursor_left_position(count=event.arg) @register('forward-word') def forward_word(event): """ Move forward to the end of the next word. Words are composed of letters and digits. """ buff = event.current_buffer pos = buff.document.find_next_word_ending(count=event.arg) if pos: buff.cursor_position += pos @register('backward-word') def backward_word(event): """ Move back to the start of the current or previous word. Words are composed of letters and digits. """ buff = event.current_buffer pos = buff.document.find_previous_word_beginning(count=event.arg) if pos: buff.cursor_position += pos @register('clear-screen') def clear_screen(event): """ Clear the screen and redraw everything at the top of the screen. """ event.app.renderer.clear() @register('redraw-current-line') def redraw_current_line(event): """ Refresh the current line. (Readline defines this command, but prompt-toolkit doesn't have it.) """ pass # # Commands for manipulating the history. # See: http://www.delorie.com/gnu/docs/readline/rlman_15.html # @register('accept-line') def accept_line(event): " Accept the line regardless of where the cursor is. " event.current_buffer.validate_and_handle() @register('previous-history') def previous_history(event): " Move `back` through the history list, fetching the previous command. " event.current_buffer.history_backward(count=event.arg) @register('next-history') def next_history(event): " Move `forward` through the history list, fetching the next command. " event.current_buffer.history_forward(count=event.arg) @register('beginning-of-history') def beginning_of_history(event): " Move to the first line in the history. " event.current_buffer.go_to_history(0) @register('end-of-history') def end_of_history(event): """ Move to the end of the input history, i.e., the line currently being entered. """ event.current_buffer.history_forward(count=10**100) buff = event.current_buffer buff.go_to_history(len(buff._working_lines) - 1) @register('reverse-search-history') def reverse_search_history(event): """ Search backward starting at the current line and moving `up` through the history as necessary. This is an incremental search. """ control = event.app.layout.current_control if control.search_buffer_control: event.app.current_search_state.direction = SearchDirection.BACKWARD event.app.layout.current_control = control.search_buffer_control # # Commands for changing text # @register('end-of-file') def end_of_file(event): """ Exit. """ event.app.exit() @register('delete-char') def delete_char(event): " Delete character before the cursor. " deleted = event.current_buffer.delete(count=event.arg) if not deleted: event.app.output.bell() @register('backward-delete-char') def backward_delete_char(event): " Delete the character behind the cursor. " if event.arg < 0: # When a negative argument has been given, this should delete in front # of the cursor. deleted = event.current_buffer.delete(count=-event.arg) else: deleted = event.current_buffer.delete_before_cursor(count=event.arg) if not deleted: event.app.output.bell() @register('self-insert') def self_insert(event): " Insert yourself. " event.current_buffer.insert_text(event.data * event.arg) @register('transpose-chars') def transpose_chars(event): """ Emulate Emacs transpose-char behavior: at the beginning of the buffer, do nothing. At the end of a line or buffer, swap the characters before the cursor. Otherwise, move the cursor right, and then swap the characters before the cursor. """ b = event.current_buffer p = b.cursor_position if p == 0: return elif p == len(b.text) or b.text[p] == '\n': b.swap_characters_before_cursor() else: b.cursor_position += b.document.get_cursor_right_position() b.swap_characters_before_cursor() @register('uppercase-word') def uppercase_word(event): """ Uppercase the current (or following) word. """ buff = event.current_buffer for i in range(event.arg): pos = buff.document.find_next_word_ending() words = buff.document.text_after_cursor[:pos] buff.insert_text(words.upper(), overwrite=True) @register('downcase-word') def downcase_word(event): """ Lowercase the current (or following) word. """ buff = event.current_buffer for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!! pos = buff.document.find_next_word_ending() words = buff.document.text_after_cursor[:pos] buff.insert_text(words.lower(), overwrite=True) @register('capitalize-word') def capitalize_word(event): """ Capitalize the current (or following) word. """ buff = event.current_buffer for i in range(event.arg): pos = buff.document.find_next_word_ending() words = buff.document.text_after_cursor[:pos] buff.insert_text(words.title(), overwrite=True) @register('quoted-insert') def quoted_insert(event): """ Add the next character typed to the line verbatim. This is how to insert key sequences like C-q, for example. """ event.app.quoted_insert = True # # Killing and yanking. # @register('kill-line') def kill_line(event): """ Kill the text from the cursor to the end of the line. If we are at the end of the line, this should remove the newline. (That way, it is possible to delete multiple lines by executing this command multiple times.) """ buff = event.current_buffer if event.arg < 0: deleted = buff.delete_before_cursor(count=-buff.document.get_start_of_line_position()) else: if buff.document.current_char == '\n': deleted = buff.delete(1) else: deleted = buff.delete(count=buff.document.get_end_of_line_position()) event.app.clipboard.set_text(deleted) @register('kill-word') def kill_word(event): """ Kill from point to the end of the current word, or if between words, to the end of the next word. Word boundaries are the same as forward-word. """ buff = event.current_buffer pos = buff.document.find_next_word_ending(count=event.arg) if pos: deleted = buff.delete(count=pos) if event.is_repeat: deleted = event.app.clipboard.get_data().text + deleted event.app.clipboard.set_text(deleted) @register('unix-word-rubout') def unix_word_rubout(event, WORD=True): """ Kill the word behind point, using whitespace as a word boundary. Usually bound to ControlW. """ buff = event.current_buffer pos = buff.document.find_start_of_previous_word(count=event.arg, WORD=WORD) if pos is None: # Nothing found? delete until the start of the document. (The # input starts with whitespace and no words were found before the # cursor.) pos = - buff.cursor_position if pos: deleted = buff.delete_before_cursor(count=-pos) # If the previous key press was also Control-W, concatenate deleted # text. if event.is_repeat: deleted += event.app.clipboard.get_data().text event.app.clipboard.set_text(deleted) else: # Nothing to delete. Bell. event.app.output.bell() @register('backward-kill-word') def backward_kill_word(event): """ Kills the word before point, using "not a letter nor a digit" as a word boundary. Usually bound to M-Del or M-Backspace. """ unix_word_rubout(event, WORD=False) @register('delete-horizontal-space') def delete_horizontal_space(event): " Delete all spaces and tabs around point. " buff = event.current_buffer text_before_cursor = buff.document.text_before_cursor text_after_cursor = buff.document.text_after_cursor delete_before = len(text_before_cursor) - len(text_before_cursor.rstrip('\t ')) delete_after = len(text_after_cursor) - len(text_after_cursor.lstrip('\t ')) buff.delete_before_cursor(count=delete_before) buff.delete(count=delete_after) @register('unix-line-discard') def unix_line_discard(event): """ Kill backward from the cursor to the beginning of the current line. """ buff = event.current_buffer if buff.document.cursor_position_col == 0 and buff.document.cursor_position > 0: buff.delete_before_cursor(count=1) else: deleted = buff.delete_before_cursor(count=-buff.document.get_start_of_line_position()) event.app.clipboard.set_text(deleted) @register('yank') def yank(event): """ Paste before cursor. """ event.current_buffer.paste_clipboard_data( event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.EMACS) @register('yank-nth-arg') def yank_nth_arg(event): """ Insert the first argument of the previous command. With an argument, insert the nth word from the previous command (start counting at 0). """ n = (event.arg if event.arg_present else None) event.current_buffer.yank_nth_arg(n) @register('yank-last-arg') def yank_last_arg(event): """ Like `yank_nth_arg`, but if no argument has been given, yank the last word of each line. """ n = (event.arg if event.arg_present else None) event.current_buffer.yank_last_arg(n) @register('yank-pop') def yank_pop(event): """ Rotate the kill ring, and yank the new top. Only works following yank or yank-pop. """ buff = event.current_buffer doc_before_paste = buff.document_before_paste clipboard = event.app.clipboard if doc_before_paste is not None: buff.document = doc_before_paste clipboard.rotate() buff.paste_clipboard_data( clipboard.get_data(), paste_mode=PasteMode.EMACS) # # Completion. # @register('complete') def complete(event): " Attempt to perform completion. " display_completions_like_readline(event) @register('menu-complete') def menu_complete(event): """ Generate completions, or go to the next completion. (This is the default way of completing input in prompt_toolkit.) """ generate_completions(event) @register('menu-complete-backward') def menu_complete_backward(event): " Move backward through the list of possible completions. " event.current_buffer.complete_previous() # # Keyboard macros. # @register('start-kbd-macro') def start_kbd_macro(event): """ Begin saving the characters typed into the current keyboard macro. """ event.app.emacs_state.start_macro() @register('end-kbd-macro') def end_kbd_macro(event): """ Stop saving the characters typed into the current keyboard macro and save the definition. """ event.app.emacs_state.end_macro() @register('call-last-kbd-macro') @key_binding(record_in_macro=False) def call_last_kbd_macro(event): """ Re-execute the last keyboard macro defined, by making the characters in the macro appear as if typed at the keyboard. Notice that we pass `record_in_macro=False`. This ensures that the 'c-x e' key sequence doesn't appear in the recording itself. This function inserts the body of the called macro back into the KeyProcessor, so these keys will be added later on to the macro of their handlers have `record_in_macro=True`. """ # Insert the macro. event.app.key_processor.feed_multiple( event.app.emacs_state.macro, first=True) @register('print-last-kbd-macro') def print_last_kbd_macro(event): " Print the last keyboard macro. " # TODO: Make the format suitable for the inputrc file. def print_macro(): for k in event.app.key_processor.macro: print(k) from prompt_toolkit.application.run_in_terminal import run_in_terminal run_in_terminal(print_macro) # # Miscellaneous Commands. # @register('undo') def undo(event): " Incremental undo. " event.current_buffer.undo() @register('insert-comment') def insert_comment(event): """ Without numeric argument, comment all lines. With numeric argument, uncomment all lines. In any case accept the input. """ buff = event.current_buffer # Transform all lines. if event.arg != 1: def change(line): return line[1:] if line.startswith('#') else line else: def change(line): return '#' + line buff.document = Document( text='\n'.join(map(change, buff.text.splitlines())), cursor_position=0) # Accept input. buff.validate_and_handle() @register('vi-editing-mode') def vi_editing_mode(event): " Switch to Vi editing mode. " event.app.editing_mode = EditingMode.VI @register('emacs-editing-mode') def emacs_editing_mode(event): " Switch to Emacs editing mode. " event.app.editing_mode = EditingMode.EMACS @register('prefix-meta') def prefix_meta(event): """ Metafy the next character typed. This is for keyboards without a meta key. Sometimes people also want to bind other keys to Meta, e.g. 'jj':: key_bindings.add_key_binding('j', 'j', filter=ViInsertMode())(prefix_meta) """ # ('first' should be true, because we want to insert it at the current # position in the queue.) event.app.key_processor.feed(KeyPress(Keys.Escape), first=True) @register('operate-and-get-next') def operate_and_get_next(event): """ Accept the current line for execution and fetch the next line relative to the current line from the history for editing. """ buff = event.current_buffer new_index = buff.working_index + 1 # Accept the current input. (This will also redraw the interface in the # 'done' state.) buff.validate_and_handle() # Set the new index at the start of the next run. def set_working_index(): if new_index < len(buff._working_lines): buff.working_index = new_index event.app.pre_run_callables.append(set_working_index) @register('edit-and-execute-command') def edit_and_execute(event): """ Invoke an editor on the current command line, and accept the result. """ buff = event.current_buffer buff.open_in_editor(validate_and_handle=True) prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/open_in_editor.py0000644000175100017510000000242013545407204031026 0ustar jonathanjonathan00000000000000""" Open in editor key bindings. """ from __future__ import unicode_literals from prompt_toolkit.filters import ( emacs_mode, has_selection, vi_navigation_mode, ) from ..key_bindings import KeyBindings, merge_key_bindings from .named_commands import get_by_name __all__ = [ 'load_open_in_editor_bindings', 'load_emacs_open_in_editor_bindings', 'load_vi_open_in_editor_bindings', ] def load_open_in_editor_bindings(): """ Load both the Vi and emacs key bindings for handling edit-and-execute-command. """ return merge_key_bindings([ load_emacs_open_in_editor_bindings(), load_vi_open_in_editor_bindings(), ]) def load_emacs_open_in_editor_bindings(): """ Pressing C-X C-E will open the buffer in an external editor. """ key_bindings = KeyBindings() key_bindings.add('c-x', 'c-e', filter=emacs_mode & ~has_selection)( get_by_name('edit-and-execute-command')) return key_bindings def load_vi_open_in_editor_bindings(): """ Pressing 'v' in navigation mode will open the buffer in an external editor. """ key_bindings = KeyBindings() key_bindings.add('v', filter=vi_navigation_mode)( get_by_name('edit-and-execute-command')) return key_bindings prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/page_navigation.py0000644000175100017510000000433613545407204031174 0ustar jonathanjonathan00000000000000""" Key bindings for extra page navigation: bindings for up/down scrolling through long pages, like in Emacs or Vi. """ from __future__ import unicode_literals from prompt_toolkit.filters import buffer_has_focus, emacs_mode, vi_mode from prompt_toolkit.key_binding.key_bindings import ( ConditionalKeyBindings, KeyBindings, merge_key_bindings, ) from .scroll import ( scroll_backward, scroll_forward, scroll_half_page_down, scroll_half_page_up, scroll_one_line_down, scroll_one_line_up, scroll_page_down, scroll_page_up, ) __all__ = [ 'load_page_navigation_bindings', 'load_emacs_page_navigation_bindings', 'load_vi_page_navigation_bindings', ] def load_page_navigation_bindings(): """ Load both the Vi and Emacs bindings for page navigation. """ # Only enable when a `Buffer` is focused, otherwise, we would catch keys # when another widget is focused (like for instance `c-d` in a # ptterm.Terminal). return ConditionalKeyBindings( merge_key_bindings([ load_emacs_page_navigation_bindings(), load_vi_page_navigation_bindings(), ]), buffer_has_focus) def load_emacs_page_navigation_bindings(): """ Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them. """ key_bindings = KeyBindings() handle = key_bindings.add handle('c-v')(scroll_page_down) handle('pagedown')(scroll_page_down) handle('escape', 'v')(scroll_page_up) handle('pageup')(scroll_page_up) return ConditionalKeyBindings(key_bindings, emacs_mode) def load_vi_page_navigation_bindings(): """ Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them. """ key_bindings = KeyBindings() handle = key_bindings.add handle('c-f')(scroll_forward) handle('c-b')(scroll_backward) handle('c-d')(scroll_half_page_down) handle('c-u')(scroll_half_page_up) handle('c-e')(scroll_one_line_down) handle('c-y')(scroll_one_line_up) handle('pagedown')(scroll_page_down) handle('pageup')(scroll_page_up) return ConditionalKeyBindings(key_bindings, vi_mode) prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/scroll.py0000644000175100017510000001236413545407204027337 0ustar jonathanjonathan00000000000000""" Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them, but they are very useful for navigating through long multiline buffers, like in Vi, Emacs, etc... """ from __future__ import unicode_literals from six.moves import range __all__ = [ 'scroll_forward', 'scroll_backward', 'scroll_half_page_up', 'scroll_half_page_down', 'scroll_one_line_up', 'scroll_one_line_down', ] def scroll_forward(event, half=False): """ Scroll window down. """ w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: info = w.render_info ui_content = info.ui_content # Height to scroll. scroll_height = info.window_height if half: scroll_height //= 2 # Calculate how many lines is equivalent to that vertical space. y = b.document.cursor_position_row + 1 height = 0 while y < ui_content.line_count: line_height = info.get_height_for_line(y) if height + line_height < scroll_height: height += line_height y += 1 else: break b.cursor_position = b.document.translate_row_col_to_index(y, 0) def scroll_backward(event, half=False): """ Scroll window up. """ w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: info = w.render_info # Height to scroll. scroll_height = info.window_height if half: scroll_height //= 2 # Calculate how many lines is equivalent to that vertical space. y = max(0, b.document.cursor_position_row - 1) height = 0 while y > 0: line_height = info.get_height_for_line(y) if height + line_height < scroll_height: height += line_height y -= 1 else: break b.cursor_position = b.document.translate_row_col_to_index(y, 0) def scroll_half_page_down(event): """ Same as ControlF, but only scroll half a page. """ scroll_forward(event, half=True) def scroll_half_page_up(event): """ Same as ControlB, but only scroll half a page. """ scroll_backward(event, half=True) def scroll_one_line_down(event): """ scroll_offset += 1 """ w = event.app.layout.current_window b = event.app.current_buffer if w: # When the cursor is at the top, move to the next line. (Otherwise, only scroll.) if w.render_info: info = w.render_info if w.vertical_scroll < info.content_height - info.window_height: if info.cursor_position.y <= info.configured_scroll_offsets.top: b.cursor_position += b.document.get_cursor_down_position() w.vertical_scroll += 1 def scroll_one_line_up(event): """ scroll_offset -= 1 """ w = event.app.layout.current_window b = event.app.current_buffer if w: # When the cursor is at the bottom, move to the previous line. (Otherwise, only scroll.) if w.render_info: info = w.render_info if w.vertical_scroll > 0: first_line_height = info.get_height_for_line(info.first_visible_line()) cursor_up = info.cursor_position.y - (info.window_height - 1 - first_line_height - info.configured_scroll_offsets.bottom) # Move cursor up, as many steps as the height of the first line. # TODO: not entirely correct yet, in case of line wrapping and many long lines. for _ in range(max(0, cursor_up)): b.cursor_position += b.document.get_cursor_up_position() # Scroll window w.vertical_scroll -= 1 def scroll_page_down(event): """ Scroll page down. (Prefer the cursor at the top of the page, after scrolling.) """ w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: # Scroll down one page. line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1) w.vertical_scroll = line_index b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) def scroll_page_up(event): """ Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.) """ w = event.app.layout.current_window b = event.app.current_buffer if w and w.render_info: # Put cursor at the first visible line. (But make sure that the cursor # moves at least one line up.) line_index = max(0, min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1)) b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) # Set the scroll offset. We can safely set it to zero; the Window will # make sure that it scrolls at least until the cursor becomes visible. w.vertical_scroll = 0 prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/search.py0000644000175100017510000000467613545407204027315 0ustar jonathanjonathan00000000000000""" Search related key bindings. """ from __future__ import unicode_literals from prompt_toolkit import search from prompt_toolkit.application.current import get_app from prompt_toolkit.filters import ( Condition, control_is_searchable, is_searching, ) from ..key_bindings import key_binding __all__ = [ 'abort_search', 'accept_search', 'start_reverse_incremental_search', 'start_forward_incremental_search', 'reverse_incremental_search', 'forward_incremental_search', 'accept_search_and_accept_input', ] @key_binding(filter=is_searching) def abort_search(event): """ Abort an incremental search and restore the original line. (Usually bound to ControlG/ControlC.) """ search.stop_search() @key_binding(filter=is_searching) def accept_search(event): """ When enter pressed in isearch, quit isearch mode. (Multiline isearch would be too complicated.) (Usually bound to Enter.) """ search.accept_search() @key_binding(filter=control_is_searchable) def start_reverse_incremental_search(event): """ Enter reverse incremental search. (Usually ControlR.) """ search.start_search(direction=search.SearchDirection.BACKWARD) @key_binding(filter=control_is_searchable) def start_forward_incremental_search(event): """ Enter forward incremental search. (Usually ControlS.) """ search.start_search(direction=search.SearchDirection.FORWARD) @key_binding(filter=is_searching) def reverse_incremental_search(event): """ Apply reverse incremental search, but keep search buffer focused. """ search.do_incremental_search( search.SearchDirection.BACKWARD, count=event.arg) @key_binding(filter=is_searching) def forward_incremental_search(event): """ Apply forward incremental search, but keep search buffer focused. """ search.do_incremental_search( search.SearchDirection.FORWARD, count=event.arg) @Condition def _previous_buffer_is_returnable(): """ True if the previously focused buffer has a return handler. """ prev_control = get_app().layout.search_target_buffer_control return prev_control and prev_control.buffer.is_returnable @key_binding(filter=is_searching & _previous_buffer_is_returnable) def accept_search_and_accept_input(event): """ Accept the search operation first, then accept the input. """ search.accept_search() event.current_buffer.validate_and_handle() prompt_toolkit-2.0.10/prompt_toolkit/key_binding/bindings/vi.py0000644000175100017510000020244013545407204026453 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals import codecs import string import six from six.moves import range from prompt_toolkit.application.current import get_app from prompt_toolkit.buffer import indent, reshape_text, unindent from prompt_toolkit.clipboard import ClipboardData from prompt_toolkit.document import Document from prompt_toolkit.filters import ( Always, Condition, has_arg, is_read_only, is_searching, ) from prompt_toolkit.filters.app import ( in_paste_mode, is_multiline, vi_digraph_mode, vi_insert_mode, vi_insert_multiple_mode, vi_mode, vi_navigation_mode, vi_recording_macro, vi_replace_mode, vi_search_direction_reversed, vi_selection_mode, vi_waiting_for_text_object_mode, ) from prompt_toolkit.input.vt100_parser import Vt100Parser from prompt_toolkit.key_binding.digraphs import DIGRAPHS from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode from prompt_toolkit.keys import Keys from prompt_toolkit.search import SearchDirection from prompt_toolkit.selection import PasteMode, SelectionState, SelectionType from ..key_bindings import ConditionalKeyBindings, KeyBindings, KeyBindingsBase from .named_commands import get_by_name try: from itertools import accumulate except ImportError: # < Python 3.2 def accumulate(iterable): " Super simple 'accumulate' implementation. " total = 0 for item in iterable: total += item yield total __all__ = [ 'load_vi_bindings', 'load_vi_search_bindings', ] if six.PY2: ascii_lowercase = string.ascii_lowercase.decode('ascii') else: ascii_lowercase = string.ascii_lowercase vi_register_names = ascii_lowercase + '0123456789' class TextObjectType(object): EXCLUSIVE = 'EXCLUSIVE' INCLUSIVE = 'INCLUSIVE' LINEWISE = 'LINEWISE' BLOCK = 'BLOCK' class TextObject(object): """ Return struct for functions wrapped in ``text_object``. Both `start` and `end` are relative to the current cursor position. """ def __init__(self, start, end=0, type=TextObjectType.EXCLUSIVE): self.start = start self.end = end self.type = type @property def selection_type(self): if self.type == TextObjectType.LINEWISE: return SelectionType.LINES if self.type == TextObjectType.BLOCK: return SelectionType.BLOCK else: return SelectionType.CHARACTERS def sorted(self): """ Return a (start, end) tuple where start <= end. """ if self.start < self.end: return self.start, self.end else: return self.end, self.start def operator_range(self, document): """ Return a (start, end) tuple with start <= end that indicates the range operators should operate on. `buffer` is used to get start and end of line positions. This should return something that can be used in a slice, so the `end` position is *not* included. """ start, end = self.sorted() doc = document if (self.type == TextObjectType.EXCLUSIVE and doc.translate_index_to_position(end + doc.cursor_position)[1] == 0): # If the motion is exclusive and the end of motion is on the first # column, the end position becomes end of previous line. end -= 1 if self.type == TextObjectType.INCLUSIVE: end += 1 if self.type == TextObjectType.LINEWISE: # Select whole lines row, col = doc.translate_index_to_position(start + doc.cursor_position) start = doc.translate_row_col_to_index(row, 0) - doc.cursor_position row, col = doc.translate_index_to_position(end + doc.cursor_position) end = doc.translate_row_col_to_index(row, len(doc.lines[row])) - doc.cursor_position return start, end def get_line_numbers(self, buffer): """ Return a (start_line, end_line) pair. """ # Get absolute cursor positions from the text object. from_, to = self.operator_range(buffer.document) from_ += buffer.cursor_position to += buffer.cursor_position # Take the start of the lines. from_, _ = buffer.document.translate_index_to_position(from_) to, _ = buffer.document.translate_index_to_position(to) return from_, to def cut(self, buffer): """ Turn text object into `ClipboardData` instance. """ from_, to = self.operator_range(buffer.document) from_ += buffer.cursor_position to += buffer.cursor_position # For Vi mode, the SelectionState does include the upper position, # while `self.operator_range` does not. So, go one to the left, unless # we're in the line mode, then we don't want to risk going to the # previous line, and missing one line in the selection. if self.type != TextObjectType.LINEWISE: to -= 1 document = Document(buffer.text, to, SelectionState( original_cursor_position=from_, type=self.selection_type)) new_document, clipboard_data = document.cut_selection() return new_document, clipboard_data def create_text_object_decorator(key_bindings): """ Create a decorator that can be used to register Vi text object implementations. """ assert isinstance(key_bindings, KeyBindingsBase) def text_object_decorator(*keys, **kw): """ Register a text object function. Usage:: @text_object('w', filter=..., no_move_handler=False) def handler(event): # Return a text object for this key. return TextObject(...) :param no_move_handler: Disable the move handler in navigation mode. (It's still active in selection mode.) """ filter = kw.pop('filter', Always()) no_move_handler = kw.pop('no_move_handler', False) no_selection_handler = kw.pop('no_selection_handler', False) eager = kw.pop('eager', False) assert not kw def decorator(text_object_func): assert callable(text_object_func) @key_bindings.add(*keys, filter=vi_waiting_for_text_object_mode & filter, eager=eager) def _(event): # Arguments are multiplied. vi_state = event.app.vi_state event._arg = (vi_state.operator_arg or 1) * (event.arg or 1) # Call the text object handler. text_obj = text_object_func(event) if text_obj is not None: assert isinstance(text_obj, TextObject) # Call the operator function with the text object. vi_state.operator_func(event, text_obj) # Clear operator. event.app.vi_state.operator_func = None event.app.vi_state.operator_arg = None # Register a move operation. (Doesn't need an operator.) if not no_move_handler: @key_bindings.add(*keys, filter=~vi_waiting_for_text_object_mode & filter & vi_navigation_mode, eager=eager) def _(event): " Move handler for navigation mode. " text_object = text_object_func(event) event.current_buffer.cursor_position += text_object.start # Register a move selection operation. if not no_selection_handler: @key_bindings.add(*keys, filter=~vi_waiting_for_text_object_mode & filter & vi_selection_mode, eager=eager) def _(event): " Move handler for selection mode. " text_object = text_object_func(event) buff = event.current_buffer # When the text object has both a start and end position, like 'i(' or 'iw', # Turn this into a selection, otherwise the cursor. if text_object.end: # Take selection positions from text object. start, end = text_object.operator_range(buff.document) start += buff.cursor_position end += buff.cursor_position buff.selection_state.original_cursor_position = start buff.cursor_position = end # Take selection type from text object. if text_object.type == TextObjectType.LINEWISE: buff.selection_state.type = SelectionType.LINES else: buff.selection_state.type = SelectionType.CHARACTERS else: event.current_buffer.cursor_position += text_object.start # Make it possible to chain @text_object decorators. return text_object_func return decorator return text_object_decorator def create_operator_decorator(key_bindings): """ Create a decorator that can be used for registering Vi operators. """ assert isinstance(key_bindings, KeyBindingsBase) def operator_decorator(*keys, **kw): """ Register a Vi operator. Usage:: @operator('d', filter=...) def handler(event, text_object): # Do something with the text object here. """ filter = kw.pop('filter', Always()) eager = kw.pop('eager', False) assert not kw def decorator(operator_func): @key_bindings.add(*keys, filter=~vi_waiting_for_text_object_mode & filter & vi_navigation_mode, eager=eager) def _(event): """ Handle operator in navigation mode. """ # When this key binding is matched, only set the operator # function in the ViState. We should execute it after a text # object has been received. event.app.vi_state.operator_func = operator_func event.app.vi_state.operator_arg = event.arg @key_bindings.add(*keys, filter=~vi_waiting_for_text_object_mode & filter & vi_selection_mode, eager=eager) def _(event): """ Handle operator in selection mode. """ buff = event.current_buffer selection_state = buff.selection_state # Create text object from selection. if selection_state.type == SelectionType.LINES: text_obj_type = TextObjectType.LINEWISE elif selection_state.type == SelectionType.BLOCK: text_obj_type = TextObjectType.BLOCK else: text_obj_type = TextObjectType.INCLUSIVE text_object = TextObject( selection_state.original_cursor_position - buff.cursor_position, type=text_obj_type) # Execute operator. operator_func(event, text_object) # Quit selection mode. buff.selection_state = None return operator_func return decorator return operator_decorator def load_vi_bindings(): """ Vi extensions. # Overview of Readline Vi commands: # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf """ # Note: Some key bindings have the "~IsReadOnly()" filter added. This # prevents the handler to be executed when the focus is on a # read-only buffer. # This is however only required for those that change the ViState to # INSERT mode. The `Buffer` class itself throws the # `EditReadOnlyBuffer` exception for any text operations which is # handled correctly. There is no need to add "~IsReadOnly" to all key # bindings that do text manipulation. key_bindings = KeyBindings() handle = key_bindings.add # (Note: Always take the navigation bindings in read-only mode, even when # ViState says different.) vi_transform_functions = [ # Rot 13 transformation (('g', '?'), Always(), lambda string: codecs.encode(string, 'rot_13')), # To lowercase (('g', 'u'), Always(), lambda string: string.lower()), # To uppercase. (('g', 'U'), Always(), lambda string: string.upper()), # Swap case. (('g', '~'), Always(), lambda string: string.swapcase()), (('~', ), Condition(lambda: get_app().vi_state.tilde_operator), lambda string: string.swapcase()), ] # Insert a character literally (quoted insert). handle('c-v', filter=vi_insert_mode)(get_by_name('quoted-insert')) @handle('escape') def _(event): """ Escape goes to vi navigation mode. """ buffer = event.current_buffer vi_state = event.app.vi_state if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): buffer.cursor_position += buffer.document.get_cursor_left_position() vi_state.input_mode = InputMode.NAVIGATION if bool(buffer.selection_state): buffer.exit_selection() @handle('k', filter=vi_selection_mode) def _(event): """ Arrow up in selection mode. """ event.current_buffer.cursor_up(count=event.arg) @handle('j', filter=vi_selection_mode) def _(event): """ Arrow down in selection mode. """ event.current_buffer.cursor_down(count=event.arg) @handle('up', filter=vi_navigation_mode) @handle('c-p', filter=vi_navigation_mode) def _(event): """ Arrow up and ControlP in navigation mode go up. """ event.current_buffer.auto_up(count=event.arg) @handle('k', filter=vi_navigation_mode) def _(event): """ Go up, but if we enter a new history entry, move to the start of the line. """ event.current_buffer.auto_up( count=event.arg, go_to_start_of_line_if_history_changes=True) @handle('down', filter=vi_navigation_mode) @handle('c-n', filter=vi_navigation_mode) def _(event): """ Arrow down and Control-N in navigation mode. """ event.current_buffer.auto_down(count=event.arg) @handle('j', filter=vi_navigation_mode) def _(event): """ Go down, but if we enter a new history entry, go to the start of the line. """ event.current_buffer.auto_down( count=event.arg, go_to_start_of_line_if_history_changes=True) @handle('backspace', filter=vi_navigation_mode) def _(event): """ In navigation-mode, move cursor. """ event.current_buffer.cursor_position += \ event.current_buffer.document.get_cursor_left_position(count=event.arg) @handle('c-n', filter=vi_insert_mode) def _(event): b = event.current_buffer if b.complete_state: b.complete_next() else: b.start_completion(select_first=True) @handle('c-p', filter=vi_insert_mode) def _(event): """ Control-P: To previous completion. """ b = event.current_buffer if b.complete_state: b.complete_previous() else: b.start_completion(select_last=True) @handle('c-g', filter=vi_insert_mode) @handle('c-y', filter=vi_insert_mode) def _(event): """ Accept current completion. """ event.current_buffer.complete_state = None @handle('c-e', filter=vi_insert_mode) def _(event): """ Cancel completion. Go back to originally typed text. """ event.current_buffer.cancel_completion() @Condition def is_returnable(): return get_app().current_buffer.is_returnable # In navigation mode, pressing enter will always return the input. handle('enter', filter=vi_navigation_mode & is_returnable)( get_by_name('accept-line')) # In insert mode, also accept input when enter is pressed, and the buffer # has been marked as single line. handle('enter', filter=is_returnable & ~is_multiline)( get_by_name('accept-line')) @handle('enter', filter=~is_returnable & vi_navigation_mode) def _(event): " Go to the beginning of next line. " b = event.current_buffer b.cursor_down(count=event.arg) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) # ** In navigation mode ** # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html @handle('insert', filter=vi_navigation_mode) def _(event): " Pressing the Insert key. " event.app.vi_state.input_mode = InputMode.INSERT @handle('insert', filter=vi_insert_mode) def _(event): " Pressing the Insert key. " event.app.vi_state.input_mode = InputMode.NAVIGATION @handle('a', filter=vi_navigation_mode & ~is_read_only) # ~IsReadOnly, because we want to stay in navigation mode for # read-only buffers. def _(event): event.current_buffer.cursor_position += event.current_buffer.document.get_cursor_right_position() event.app.vi_state.input_mode = InputMode.INSERT @handle('A', filter=vi_navigation_mode & ~is_read_only) def _(event): event.current_buffer.cursor_position += event.current_buffer.document.get_end_of_line_position() event.app.vi_state.input_mode = InputMode.INSERT @handle('C', filter=vi_navigation_mode & ~is_read_only) def _(event): """ # Change to end of line. # Same as 'c$' (which is implemented elsewhere.) """ buffer = event.current_buffer deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) event.app.clipboard.set_text(deleted) event.app.vi_state.input_mode = InputMode.INSERT @handle('c', 'c', filter=vi_navigation_mode & ~is_read_only) @handle('S', filter=vi_navigation_mode & ~is_read_only) def _(event): # TODO: implement 'arg' """ Change current line """ buffer = event.current_buffer # We copy the whole line. data = ClipboardData(buffer.document.current_line, SelectionType.LINES) event.app.clipboard.set_data(data) # But we delete after the whitespace buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) buffer.delete(count=buffer.document.get_end_of_line_position()) event.app.vi_state.input_mode = InputMode.INSERT @handle('D', filter=vi_navigation_mode) def _(event): buffer = event.current_buffer deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) event.app.clipboard.set_text(deleted) @handle('d', 'd', filter=vi_navigation_mode) def _(event): """ Delete line. (Or the following 'n' lines.) """ buffer = event.current_buffer # Split string in before/deleted/after text. lines = buffer.document.lines before = '\n'.join(lines[:buffer.document.cursor_position_row]) deleted = '\n'.join(lines[buffer.document.cursor_position_row: buffer.document.cursor_position_row + event.arg]) after = '\n'.join(lines[buffer.document.cursor_position_row + event.arg:]) # Set new text. if before and after: before = before + '\n' # Set text and cursor position. buffer.document = Document( text=before + after, # Cursor At the start of the first 'after' line, after the leading whitespace. cursor_position = len(before) + len(after) - len(after.lstrip(' '))) # Set clipboard data event.app.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES)) @handle('x', filter=vi_selection_mode) def _(event): """ Cut selection. ('x' is not an operator.) """ clipboard_data = event.current_buffer.cut_selection() event.app.clipboard.set_data(clipboard_data) @handle('i', filter=vi_navigation_mode & ~is_read_only) def _(event): event.app.vi_state.input_mode = InputMode.INSERT @handle('I', filter=vi_navigation_mode & ~is_read_only) def _(event): event.app.vi_state.input_mode = InputMode.INSERT event.current_buffer.cursor_position += \ event.current_buffer.document.get_start_of_line_position(after_whitespace=True) @Condition def in_block_selection(): buff = get_app().current_buffer return buff.selection_state and buff.selection_state.type == SelectionType.BLOCK @handle('I', filter=in_block_selection & ~is_read_only) def go_to_block_selection(event, after=False): " Insert in block selection mode. " buff = event.current_buffer # Store all cursor positions. positions = [] if after: def get_pos(from_to): return from_to[1] else: def get_pos(from_to): return from_to[0] for i, from_to in enumerate(buff.document.selection_ranges()): positions.append(get_pos(from_to)) if i == 0: buff.cursor_position = get_pos(from_to) buff.multiple_cursor_positions = positions # Go to 'INSERT_MULTIPLE' mode. event.app.vi_state.input_mode = InputMode.INSERT_MULTIPLE buff.exit_selection() @handle('A', filter=in_block_selection & ~is_read_only) def _(event): go_to_block_selection(event, after=True) @handle('J', filter=vi_navigation_mode & ~is_read_only) def _(event): " Join lines. " for i in range(event.arg): event.current_buffer.join_next_line() @handle('g', 'J', filter=vi_navigation_mode & ~is_read_only) def _(event): " Join lines without space. " for i in range(event.arg): event.current_buffer.join_next_line(separator='') @handle('J', filter=vi_selection_mode & ~is_read_only) def _(event): " Join selected lines. " event.current_buffer.join_selected_lines() @handle('g', 'J', filter=vi_selection_mode & ~is_read_only) def _(event): " Join selected lines without space. " event.current_buffer.join_selected_lines(separator='') @handle('p', filter=vi_navigation_mode) def _(event): """ Paste after """ event.current_buffer.paste_clipboard_data( event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.VI_AFTER) @handle('P', filter=vi_navigation_mode) def _(event): """ Paste before """ event.current_buffer.paste_clipboard_data( event.app.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.VI_BEFORE) @handle('"', Keys.Any, 'p', filter=vi_navigation_mode) def _(event): " Paste from named register. " c = event.key_sequence[1].data if c in vi_register_names: data = event.app.vi_state.named_registers.get(c) if data: event.current_buffer.paste_clipboard_data( data, count=event.arg, paste_mode=PasteMode.VI_AFTER) @handle('"', Keys.Any, 'P', filter=vi_navigation_mode) def _(event): " Paste (before) from named register. " c = event.key_sequence[1].data if c in vi_register_names: data = event.app.vi_state.named_registers.get(c) if data: event.current_buffer.paste_clipboard_data( data, count=event.arg, paste_mode=PasteMode.VI_BEFORE) @handle('r', Keys.Any, filter=vi_navigation_mode) def _(event): """ Replace single character under cursor """ event.current_buffer.insert_text(event.data * event.arg, overwrite=True) event.current_buffer.cursor_position -= 1 @handle('R', filter=vi_navigation_mode) def _(event): """ Go to 'replace'-mode. """ event.app.vi_state.input_mode = InputMode.REPLACE @handle('s', filter=vi_navigation_mode & ~is_read_only) def _(event): """ Substitute with new text (Delete character(s) and go to insert mode.) """ text = event.current_buffer.delete(count=event.arg) event.app.clipboard.set_text(text) event.app.vi_state.input_mode = InputMode.INSERT @handle('u', filter=vi_navigation_mode, save_before=(lambda e: False)) def _(event): for i in range(event.arg): event.current_buffer.undo() @handle('V', filter=vi_navigation_mode) def _(event): """ Start lines selection. """ event.current_buffer.start_selection(selection_type=SelectionType.LINES) @handle('c-v', filter=vi_navigation_mode) def _(event): " Enter block selection mode. " event.current_buffer.start_selection(selection_type=SelectionType.BLOCK) @handle('V', filter=vi_selection_mode) def _(event): """ Exit line selection mode, or go from non line selection mode to line selection mode. """ selection_state = event.current_buffer.selection_state if selection_state.type != SelectionType.LINES: selection_state.type = SelectionType.LINES else: event.current_buffer.exit_selection() @handle('v', filter=vi_navigation_mode) def _(event): " Enter character selection mode. " event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) @handle('v', filter=vi_selection_mode) def _(event): """ Exit character selection mode, or go from non-character-selection mode to character selection mode. """ selection_state = event.current_buffer.selection_state if selection_state.type != SelectionType.CHARACTERS: selection_state.type = SelectionType.CHARACTERS else: event.current_buffer.exit_selection() @handle('c-v', filter=vi_selection_mode) def _(event): """ Exit block selection mode, or go from non block selection mode to block selection mode. """ selection_state = event.current_buffer.selection_state if selection_state.type != SelectionType.BLOCK: selection_state.type = SelectionType.BLOCK else: event.current_buffer.exit_selection() @handle('a', 'w', filter=vi_selection_mode) @handle('a', 'W', filter=vi_selection_mode) def _(event): """ Switch from visual linewise mode to visual characterwise mode. """ buffer = event.current_buffer if buffer.selection_state and buffer.selection_state.type == SelectionType.LINES: buffer.selection_state.type = SelectionType.CHARACTERS @handle('x', filter=vi_navigation_mode) def _(event): """ Delete character. """ buff = event.current_buffer count = min(event.arg, len(buff.document.current_line_after_cursor)) if count: text = event.current_buffer.delete(count=count) event.app.clipboard.set_text(text) @handle('X', filter=vi_navigation_mode) def _(event): buff = event.current_buffer count = min(event.arg, len(buff.document.current_line_before_cursor)) if count: text = event.current_buffer.delete_before_cursor(count=count) event.app.clipboard.set_text(text) @handle('y', 'y', filter=vi_navigation_mode) @handle('Y', filter=vi_navigation_mode) def _(event): """ Yank the whole line. """ text = '\n'.join(event.current_buffer.document.lines_from_current[:event.arg]) event.app.clipboard.set_data(ClipboardData(text, SelectionType.LINES)) @handle('+', filter=vi_navigation_mode) def _(event): """ Move to first non whitespace of next line """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_down_position(count=event.arg) buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) @handle('-', filter=vi_navigation_mode) def _(event): """ Move to first non whitespace of previous line """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_up_position(count=event.arg) buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=True) @handle('>', '>', filter=vi_navigation_mode) def _(event): """ Indent lines. """ buffer = event.current_buffer current_row = buffer.document.cursor_position_row indent(buffer, current_row, current_row + event.arg) @handle('<', '<', filter=vi_navigation_mode) def _(event): """ Unindent lines. """ current_row = event.current_buffer.document.cursor_position_row unindent(event.current_buffer, current_row, current_row + event.arg) @handle('O', filter=vi_navigation_mode & ~is_read_only) def _(event): """ Open line above and enter insertion mode """ event.current_buffer.insert_line_above( copy_margin=not in_paste_mode()) event.app.vi_state.input_mode = InputMode.INSERT @handle('o', filter=vi_navigation_mode & ~is_read_only) def _(event): """ Open line below and enter insertion mode """ event.current_buffer.insert_line_below( copy_margin=not in_paste_mode()) event.app.vi_state.input_mode = InputMode.INSERT @handle('~', filter=vi_navigation_mode) def _(event): """ Reverse case of current character and move cursor forward. """ buffer = event.current_buffer c = buffer.document.current_char if c is not None and c != '\n': buffer.insert_text(c.swapcase(), overwrite=True) @handle('g', 'u', 'u', filter=vi_navigation_mode & ~is_read_only) def _(event): " Lowercase current line. " buff = event.current_buffer buff.transform_current_line(lambda s: s.lower()) @handle('g', 'U', 'U', filter=vi_navigation_mode & ~is_read_only) def _(event): " Uppercase current line. " buff = event.current_buffer buff.transform_current_line(lambda s: s.upper()) @handle('g', '~', '~', filter=vi_navigation_mode & ~is_read_only) def _(event): " Swap case of the current line. " buff = event.current_buffer buff.transform_current_line(lambda s: s.swapcase()) @handle('#', filter=vi_navigation_mode) def _(event): """ Go to previous occurrence of this word. """ b = event.current_buffer search_state = event.app.current_search_state search_state.text = b.document.get_word_under_cursor() search_state.direction = SearchDirection.BACKWARD b.apply_search(search_state, count=event.arg, include_current_position=False) @handle('*', filter=vi_navigation_mode) def _(event): """ Go to next occurrence of this word. """ b = event.current_buffer search_state = event.app.current_search_state search_state.text = b.document.get_word_under_cursor() search_state.direction = SearchDirection.FORWARD b.apply_search(search_state, count=event.arg, include_current_position=False) @handle('(', filter=vi_navigation_mode) def _(event): # TODO: go to begin of sentence. # XXX: should become text_object. pass @handle(')', filter=vi_navigation_mode) def _(event): # TODO: go to end of sentence. # XXX: should become text_object. pass operator = create_operator_decorator(key_bindings) text_object = create_text_object_decorator(key_bindings) @text_object(Keys.Any, filter=vi_waiting_for_text_object_mode) def _(event): """ Unknown key binding while waiting for a text object. """ event.app.output.bell() # # *** Operators *** # def create_delete_and_change_operators(delete_only, with_register=False): """ Delete and change operators. :param delete_only: Create an operator that deletes, but doesn't go to insert mode. :param with_register: Copy the deleted text to this named register instead of the clipboard. """ if with_register: handler_keys = ('"', Keys.Any, 'cd'[delete_only]) else: handler_keys = 'cd'[delete_only] @operator(*handler_keys, filter=~is_read_only) def delete_or_change_operator(event, text_object): clipboard_data = None buff = event.current_buffer if text_object: new_document, clipboard_data = text_object.cut(buff) buff.document = new_document # Set deleted/changed text to clipboard or named register. if clipboard_data and clipboard_data.text: if with_register: reg_name = event.key_sequence[1].data if reg_name in vi_register_names: event.app.vi_state.named_registers[reg_name] = clipboard_data else: event.app.clipboard.set_data(clipboard_data) # Only go back to insert mode in case of 'change'. if not delete_only: event.app.vi_state.input_mode = InputMode.INSERT create_delete_and_change_operators(False, False) create_delete_and_change_operators(False, True) create_delete_and_change_operators(True, False) create_delete_and_change_operators(True, True) def create_transform_handler(filter, transform_func, *a): @operator(*a, filter=filter & ~is_read_only) def _(event, text_object): """ Apply transformation (uppercase, lowercase, rot13, swap case). """ buff = event.current_buffer start, end = text_object.operator_range(buff.document) if start < end: # Transform. buff.transform_region( buff.cursor_position + start, buff.cursor_position + end, transform_func) # Move cursor buff.cursor_position += (text_object.end or text_object.start) for k, f, func in vi_transform_functions: create_transform_handler(f, func, *k) @operator('y') def yank_handler(event, text_object): """ Yank operator. (Copy text.) """ _, clipboard_data = text_object.cut(event.current_buffer) if clipboard_data.text: event.app.clipboard.set_data(clipboard_data) @operator('"', Keys.Any, 'y') def _(event, text_object): " Yank selection to named register. " c = event.key_sequence[1].data if c in vi_register_names: _, clipboard_data = text_object.cut(event.current_buffer) event.app.vi_state.named_registers[c] = clipboard_data @operator('>') def _(event, text_object): """ Indent. """ buff = event.current_buffer from_, to = text_object.get_line_numbers(buff) indent(buff, from_, to + 1, count=event.arg) @operator('<') def _(event, text_object): """ Unindent. """ buff = event.current_buffer from_, to = text_object.get_line_numbers(buff) unindent(buff, from_, to + 1, count=event.arg) @operator('g', 'q') def _(event, text_object): """ Reshape text. """ buff = event.current_buffer from_, to = text_object.get_line_numbers(buff) reshape_text(buff, from_, to) # # *** Text objects *** # @text_object('b') def _(event): """ Move one word or token left. """ return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg) or 0) @text_object('B') def _(event): """ Move one non-blank word left """ return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg, WORD=True) or 0) @text_object('$') def key_dollar(event): """ 'c$', 'd$' and '$': Delete/change/move until end of line. """ return TextObject(event.current_buffer.document.get_end_of_line_position()) @text_object('w') def _(event): """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. """ return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg) or event.current_buffer.document.get_end_of_document_position()) @text_object('W') def _(event): """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. """ return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg, WORD=True) or event.current_buffer.document.get_end_of_document_position()) @text_object('e') def _(event): """ End of 'word': 'ce', 'de', 'e' """ end = event.current_buffer.document.find_next_word_ending(count=event.arg) return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) @text_object('E') def _(event): """ End of 'WORD': 'cE', 'dE', 'E' """ end = event.current_buffer.document.find_next_word_ending(count=event.arg, WORD=True) return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) @text_object('i', 'w', no_move_handler=True) def _(event): """ Inner 'word': ciw and diw """ start, end = event.current_buffer.document.find_boundaries_of_current_word() return TextObject(start, end) @text_object('a', 'w', no_move_handler=True) def _(event): """ A 'word': caw and daw """ start, end = event.current_buffer.document.find_boundaries_of_current_word(include_trailing_whitespace=True) return TextObject(start, end) @text_object('i', 'W', no_move_handler=True) def _(event): """ Inner 'WORD': ciW and diW """ start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True) return TextObject(start, end) @text_object('a', 'W', no_move_handler=True) def _(event): """ A 'WORD': caw and daw """ start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True, include_trailing_whitespace=True) return TextObject(start, end) @text_object('a', 'p', no_move_handler=True) def _(event): """ Auto paragraph. """ start = event.current_buffer.document.start_of_paragraph() end = event.current_buffer.document.end_of_paragraph(count=event.arg) return TextObject(start, end) @text_object('^') def key_circumflex(event): """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """ return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=True)) @text_object('0') def key_zero(event): """ 'c0', 'd0': Hard start of line, before whitespace. (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) """ return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=False)) def create_ci_ca_handles(ci_start, ci_end, inner, key=None): # TODO: 'dat', 'dit', (tags (like xml) """ Delete/Change string between this start and stop character. But keep these characters. This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. """ def handler(event): if ci_start == ci_end: # Quotes start = event.current_buffer.document.find_backwards(ci_start, in_current_line=False) end = event.current_buffer.document.find(ci_end, in_current_line=False) else: # Brackets start = event.current_buffer.document.find_enclosing_bracket_left(ci_start, ci_end) end = event.current_buffer.document.find_enclosing_bracket_right(ci_start, ci_end) if start is not None and end is not None: offset = 0 if inner else 1 return TextObject(start + 1 - offset, end + offset) else: # Nothing found. return TextObject(0) if key is None: text_object('ai'[inner], ci_start, no_move_handler=True)(handler) text_object('ai'[inner], ci_end, no_move_handler=True)(handler) else: text_object('ai'[inner], key, no_move_handler=True)(handler) for inner in (False, True): for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"), ('[', ']'), ('<', '>'), ('{', '}'), ('(', ')')]: create_ci_ca_handles(ci_start, ci_end, inner) create_ci_ca_handles('(', ')', inner, 'b') # 'dab', 'dib' create_ci_ca_handles('{', '}', inner, 'B') # 'daB', 'diB' @text_object('{') def _(event): """ Move to previous blank-line separated section. Implements '{', 'c{', 'd{', 'y{' """ index = event.current_buffer.document.start_of_paragraph( count=event.arg, before=True) return TextObject(index) @text_object('}') def _(event): """ Move to next blank-line separated section. Implements '}', 'c}', 'd}', 'y}' """ index = event.current_buffer.document.end_of_paragraph(count=event.arg, after=True) return TextObject(index) @text_object('f', Keys.Any) def _(event): """ Go to next occurrence of character. Typing 'fx' will move the cursor to the next occurrence of character. 'x'. """ event.app.vi_state.last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find( event.data, in_current_line=True, count=event.arg) if match: return TextObject(match, type=TextObjectType.INCLUSIVE) else: return TextObject(0) @text_object('F', Keys.Any) def _(event): """ Go to previous occurrence of character. Typing 'Fx' will move the cursor to the previous occurrence of character. 'x'. """ event.app.vi_state.last_character_find = CharacterFind(event.data, True) return TextObject(event.current_buffer.document.find_backwards( event.data, in_current_line=True, count=event.arg) or 0) @text_object('t', Keys.Any) def _(event): """ Move right to the next occurrence of c, then one char backward. """ event.app.vi_state.last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find( event.data, in_current_line=True, count=event.arg) if match: return TextObject(match - 1, type=TextObjectType.INCLUSIVE) else: return TextObject(0) @text_object('T', Keys.Any) def _(event): """ Move left to the previous occurrence of c, then one char forward. """ event.app.vi_state.last_character_find = CharacterFind(event.data, True) match = event.current_buffer.document.find_backwards( event.data, in_current_line=True, count=event.arg) return TextObject(match + 1 if match else 0) def repeat(reverse): """ Create ',' and ';' commands. """ @text_object(',' if reverse else ';') def _(event): # Repeat the last 'f'/'F'/'t'/'T' command. pos = 0 vi_state = event.app.vi_state type = TextObjectType.EXCLUSIVE if vi_state.last_character_find: char = vi_state.last_character_find.character backwards = vi_state.last_character_find.backwards if reverse: backwards = not backwards if backwards: pos = event.current_buffer.document.find_backwards(char, in_current_line=True, count=event.arg) else: pos = event.current_buffer.document.find(char, in_current_line=True, count=event.arg) type = TextObjectType.INCLUSIVE if pos: return TextObject(pos, type=type) else: return TextObject(0) repeat(True) repeat(False) @text_object('h') @text_object('left') def _(event): """ Implements 'ch', 'dh', 'h': Cursor left. """ return TextObject(event.current_buffer.document.get_cursor_left_position(count=event.arg)) @text_object('j', no_move_handler=True, no_selection_handler=True) # Note: We also need `no_selection_handler`, because we in # selection mode, we prefer the other 'j' binding that keeps # `buffer.preferred_column`. def _(event): """ Implements 'cj', 'dj', 'j', ... Cursor up. """ return TextObject(event.current_buffer.document.get_cursor_down_position(count=event.arg), type=TextObjectType.LINEWISE) @text_object('k', no_move_handler=True, no_selection_handler=True) def _(event): """ Implements 'ck', 'dk', 'k', ... Cursor up. """ return TextObject(event.current_buffer.document.get_cursor_up_position(count=event.arg), type=TextObjectType.LINEWISE) @text_object('l') @text_object(' ') @text_object('right') def _(event): """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """ return TextObject(event.current_buffer.document.get_cursor_right_position(count=event.arg)) @text_object('H') def _(event): """ Moves to the start of the visible region. (Below the scroll offset.) Implements 'cH', 'dH', 'H'. """ w = event.app.layout.current_window b = event.current_buffer if w and w.render_info: # When we find a Window that has BufferControl showing this window, # move to the start of the visible area. pos = (b.document.translate_row_col_to_index( w.render_info.first_visible_line(after_scroll_offset=True), 0) - b.cursor_position) else: # Otherwise, move to the start of the input. pos = -len(b.document.text_before_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) @text_object('M') def _(event): """ Moves cursor to the vertical center of the visible region. Implements 'cM', 'dM', 'M'. """ w = event.app.layout.current_window b = event.current_buffer if w and w.render_info: # When we find a Window that has BufferControl showing this window, # move to the center of the visible area. pos = (b.document.translate_row_col_to_index( w.render_info.center_visible_line(), 0) - b.cursor_position) else: # Otherwise, move to the start of the input. pos = -len(b.document.text_before_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) @text_object('L') def _(event): """ Moves to the end of the visible region. (Above the scroll offset.) """ w = event.app.layout.current_window b = event.current_buffer if w and w.render_info: # When we find a Window that has BufferControl showing this window, # move to the end of the visible area. pos = (b.document.translate_row_col_to_index( w.render_info.last_visible_line(before_scroll_offset=True), 0) - b.cursor_position) else: # Otherwise, move to the end of the input. pos = len(b.document.text_after_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) @text_object('n', no_move_handler=True) def _(event): " Search next. " buff = event.current_buffer search_state = event.app.current_search_state cursor_position = buff.get_search_position( search_state, include_current_position=False, count=event.arg) return TextObject(cursor_position - buff.cursor_position) @handle('n', filter=vi_navigation_mode) def _(event): " Search next in navigation mode. (This goes through the history.) " search_state = event.app.current_search_state event.current_buffer.apply_search( search_state, include_current_position=False, count=event.arg) @text_object('N', no_move_handler=True) def _(event): " Search previous. " buff = event.current_buffer search_state = event.app.current_search_state cursor_position = buff.get_search_position( ~search_state, include_current_position=False, count=event.arg) return TextObject(cursor_position - buff.cursor_position) @handle('N', filter=vi_navigation_mode) def _(event): " Search previous in navigation mode. (This goes through the history.) " search_state = event.app.current_search_state event.current_buffer.apply_search( ~search_state, include_current_position=False, count=event.arg) @handle('z', '+', filter=vi_navigation_mode|vi_selection_mode) @handle('z', 't', filter=vi_navigation_mode|vi_selection_mode) @handle('z', 'enter', filter=vi_navigation_mode|vi_selection_mode) def _(event): """ Scrolls the window to makes the current line the first line in the visible region. """ b = event.current_buffer event.app.layout.current_window.vertical_scroll = b.document.cursor_position_row @handle('z', '-', filter=vi_navigation_mode|vi_selection_mode) @handle('z', 'b', filter=vi_navigation_mode|vi_selection_mode) def _(event): """ Scrolls the window to makes the current line the last line in the visible region. """ # We can safely set the scroll offset to zero; the Window will make # sure that it scrolls at least enough to make the cursor visible # again. event.app.layout.current_window.vertical_scroll = 0 @handle('z', 'z', filter=vi_navigation_mode|vi_selection_mode) def _(event): """ Center Window vertically around cursor. """ w = event.app.layout.current_window b = event.current_buffer if w and w.render_info: info = w.render_info # Calculate the offset that we need in order to position the row # containing the cursor in the center. scroll_height = info.window_height // 2 y = max(0, b.document.cursor_position_row - 1) height = 0 while y > 0: line_height = info.get_height_for_line(y) if height + line_height < scroll_height: height += line_height y -= 1 else: break w.vertical_scroll = y @text_object('%') def _(event): """ Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) If an 'arg' has been given, go this this % position in the file. """ buffer = event.current_buffer if event._arg: # If 'arg' has been given, the meaning of % is to go to the 'x%' # row in the file. if 0 < event.arg <= 100: absolute_index = buffer.document.translate_row_col_to_index( int((event.arg * buffer.document.line_count - 1) / 100), 0) return TextObject(absolute_index - buffer.document.cursor_position, type=TextObjectType.LINEWISE) else: return TextObject(0) # Do nothing. else: # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). match = buffer.document.find_matching_bracket_position() if match: return TextObject(match, type=TextObjectType.INCLUSIVE) else: return TextObject(0) @text_object('|') def _(event): # Move to the n-th column (you may specify the argument n by typing # it on number keys, for example, 20|). return TextObject(event.current_buffer.document.get_column_cursor_position(event.arg - 1)) @text_object('g', 'g') def _(event): """ Implements 'gg', 'cgg', 'ygg' """ d = event.current_buffer.document if event._arg: # Move to the given line. return TextObject(d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, type=TextObjectType.LINEWISE) else: # Move to the top of the input. return TextObject(d.get_start_of_document_position(), type=TextObjectType.LINEWISE) @text_object('g', '_') def _(event): """ Go to last non-blank of line. 'g_', 'cg_', 'yg_', etc.. """ return TextObject( event.current_buffer.document.last_non_blank_of_current_line_position(), type=TextObjectType.INCLUSIVE) @text_object('g', 'e') def _(event): """ Go to last character of previous word. 'ge', 'cge', 'yge', etc.. """ prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg) return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) @text_object('g', 'E') def _(event): """ Go to last character of previous WORD. 'gE', 'cgE', 'ygE', etc.. """ prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg, WORD=True) return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) @text_object('g', 'm') def _(event): """ Like g0, but half a screenwidth to the right. (Or as much as possible.) """ w = event.app.layout.current_window buff = event.current_buffer if w and w.render_info: width = w.render_info.window_width start = buff.document.get_start_of_line_position(after_whitespace=False) start += int(min(width / 2, len(buff.document.current_line))) return TextObject(start, type=TextObjectType.INCLUSIVE) return TextObject(0) @text_object('G') def _(event): """ Go to the end of the document. (If no arg has been given.) """ buf = event.current_buffer return TextObject(buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) - buf.cursor_position, type=TextObjectType.LINEWISE) # # *** Other *** # @handle('G', filter=has_arg) def _(event): """ If an argument is given, move to this line in the history. (for example, 15G) """ event.current_buffer.go_to_history(event.arg - 1) for n in '123456789': @handle(n, filter=vi_navigation_mode|vi_selection_mode|vi_waiting_for_text_object_mode) def _(event): """ Always handle numberics in navigation mode as arg. """ event.append_to_arg_count(event.data) @handle('0', filter=(vi_navigation_mode|vi_selection_mode|vi_waiting_for_text_object_mode) & has_arg) def _(event): " Zero when an argument was already give. " event.append_to_arg_count(event.data) @handle(Keys.Any, filter=vi_replace_mode) def _(event): """ Insert data at cursor position. """ event.current_buffer.insert_text(event.data, overwrite=True) @handle(Keys.Any, filter=vi_insert_multiple_mode, save_before=(lambda e: not e.is_repeat)) def _(event): """ Insert data at multiple cursor positions at once. (Usually a result of pressing 'I' or 'A' in block-selection mode.) """ buff = event.current_buffer original_text = buff.text # Construct new text. text = [] p = 0 for p2 in buff.multiple_cursor_positions: text.append(original_text[p:p2]) text.append(event.data) p = p2 text.append(original_text[p:]) # Shift all cursor positions. new_cursor_positions = [ pos + i + 1 for i, pos in enumerate(buff.multiple_cursor_positions)] # Set result. buff.text = ''.join(text) buff.multiple_cursor_positions = new_cursor_positions buff.cursor_position += 1 @handle('backspace', filter=vi_insert_multiple_mode) def _(event): " Backspace, using multiple cursors. " buff = event.current_buffer original_text = buff.text # Construct new text. deleted_something = False text = [] p = 0 for p2 in buff.multiple_cursor_positions: if p2 > 0 and original_text[p2 - 1] != '\n': # Don't delete across lines. text.append(original_text[p:p2 - 1]) deleted_something = True else: text.append(original_text[p:p2]) p = p2 text.append(original_text[p:]) if deleted_something: # Shift all cursor positions. lengths = [len(part) for part in text[:-1]] new_cursor_positions = list(accumulate(lengths)) # Set result. buff.text = ''.join(text) buff.multiple_cursor_positions = new_cursor_positions buff.cursor_position -= 1 else: event.app.output.bell() @handle('delete', filter=vi_insert_multiple_mode) def _(event): " Delete, using multiple cursors. " buff = event.current_buffer original_text = buff.text # Construct new text. deleted_something = False text = [] new_cursor_positions = [] p = 0 for p2 in buff.multiple_cursor_positions: text.append(original_text[p:p2]) if p2 >= len(original_text) or original_text[p2] == '\n': # Don't delete across lines. p = p2 else: p = p2 + 1 deleted_something = True text.append(original_text[p:]) if deleted_something: # Shift all cursor positions. lengths = [len(part) for part in text[:-1]] new_cursor_positions = list(accumulate(lengths)) # Set result. buff.text = ''.join(text) buff.multiple_cursor_positions = new_cursor_positions else: event.app.output.bell() @handle('left', filter=vi_insert_multiple_mode) def _(event): """ Move all cursors to the left. (But keep all cursors on the same line.) """ buff = event.current_buffer new_positions = [] for p in buff.multiple_cursor_positions: if buff.document.translate_index_to_position(p)[1] > 0: p -= 1 new_positions.append(p) buff.multiple_cursor_positions = new_positions if buff.document.cursor_position_col > 0: buff.cursor_position -= 1 @handle('right', filter=vi_insert_multiple_mode) def _(event): """ Move all cursors to the right. (But keep all cursors on the same line.) """ buff = event.current_buffer new_positions = [] for p in buff.multiple_cursor_positions: row, column = buff.document.translate_index_to_position(p) if column < len(buff.document.lines[row]): p += 1 new_positions.append(p) buff.multiple_cursor_positions = new_positions if not buff.document.is_cursor_at_the_end_of_line: buff.cursor_position += 1 @handle('up', filter=vi_insert_multiple_mode) @handle('down', filter=vi_insert_multiple_mode) def _(event): " Ignore all up/down key presses when in multiple cursor mode. " @handle('c-x', 'c-l', filter=vi_insert_mode) def _(event): """ Pressing the ControlX - ControlL sequence in Vi mode does line completion based on the other lines in the document and the history. """ event.current_buffer.start_history_lines_completion() @handle('c-x', 'c-f', filter=vi_insert_mode) def _(event): """ Complete file names. """ # TODO pass @handle('c-k', filter=vi_insert_mode|vi_replace_mode) def _(event): " Go into digraph mode. " event.app.vi_state.waiting_for_digraph = True @Condition def digraph_symbol_1_given(): return get_app().vi_state.digraph_symbol1 is not None @handle(Keys.Any, filter=vi_digraph_mode & ~digraph_symbol_1_given) def _(event): event.app.vi_state.digraph_symbol1 = event.data @handle(Keys.Any, filter=vi_digraph_mode & digraph_symbol_1_given) def _(event): " Insert digraph. " try: # Lookup. code = (event.app.vi_state.digraph_symbol1, event.data) if code not in DIGRAPHS: code = code[::-1] # Try reversing. symbol = DIGRAPHS[code] except KeyError: # Unknown digraph. event.app.output.bell() else: # Insert digraph. overwrite = event.app.vi_state.input_mode == InputMode.REPLACE event.current_buffer.insert_text( six.unichr(symbol), overwrite=overwrite) event.app.vi_state.waiting_for_digraph = False finally: event.app.vi_state.waiting_for_digraph = False event.app.vi_state.digraph_symbol1 = None @handle('c-o', filter=vi_insert_mode | vi_replace_mode) def _(event): " Go into normal mode for one single action. " event.app.vi_state.temporary_navigation_mode = True @handle('q', Keys.Any, filter=vi_navigation_mode & ~vi_recording_macro) def _(event): " Start recording macro. " c = event.key_sequence[1].data if c in vi_register_names: vi_state = event.app.vi_state vi_state.recording_register = c vi_state.current_recording = '' @handle('q', filter=vi_navigation_mode & vi_recording_macro) def _(event): " Stop recording macro. " vi_state = event.app.vi_state # Store and stop recording. vi_state.named_registers[vi_state.recording_register] = ClipboardData(vi_state.current_recording) vi_state.recording_register = None vi_state.current_recording = '' @handle('@', Keys.Any, filter=vi_navigation_mode, record_in_macro=False) def _(event): """ Execute macro. Notice that we pass `record_in_macro=False`. This ensures that the `@x` keys don't appear in the recording itself. This function inserts the body of the called macro back into the KeyProcessor, so these keys will be added later on to the macro of their handlers have `record_in_macro=True`. """ # Retrieve macro. c = event.key_sequence[1].data try: macro = event.app.vi_state.named_registers[c] except KeyError: return # Expand macro (which is a string in the register), in individual keys. # Use vt100 parser for this. keys = [] parser = Vt100Parser(keys.append) parser.feed(macro.text) parser.flush() # Now feed keys back to the input processor. for _ in range(event.arg): event.app.key_processor.feed_multiple(keys, first=True) return ConditionalKeyBindings(key_bindings, vi_mode) def load_vi_search_bindings(): key_bindings = KeyBindings() handle = key_bindings.add from . import search @Condition def search_buffer_is_empty(): " Returns True when the search buffer is empty. " return get_app().current_buffer.text == '' # Vi-style forward search. handle('/', filter=(vi_navigation_mode|vi_selection_mode)&~vi_search_direction_reversed) \ (search.start_forward_incremental_search) handle('?', filter=(vi_navigation_mode|vi_selection_mode)&vi_search_direction_reversed) \ (search.start_forward_incremental_search) handle('c-s')(search.start_forward_incremental_search) # Vi-style backward search. handle('?', filter=(vi_navigation_mode|vi_selection_mode)&~vi_search_direction_reversed) \ (search.start_reverse_incremental_search) handle('/', filter=(vi_navigation_mode|vi_selection_mode)&vi_search_direction_reversed) \ (search.start_reverse_incremental_search) handle('c-r')(search.start_reverse_incremental_search) # Apply the search. (At the / or ? prompt.) handle('enter', filter=is_searching)(search.accept_search) handle('c-r', filter=is_searching)(search.reverse_incremental_search) handle('c-s', filter=is_searching)(search.forward_incremental_search) handle('c-c')(search.abort_search) handle('c-g')(search.abort_search) handle('backspace', filter=search_buffer_is_empty)(search.abort_search) # Handle escape. This should accept the search, just like readline. # `abort_search` would be a meaningful alternative. handle('escape')(search.accept_search) return ConditionalKeyBindings(key_bindings, vi_mode) prompt_toolkit-2.0.10/prompt_toolkit/key_binding/defaults.py0000644000175100017510000000332613545407204026051 0ustar jonathanjonathan00000000000000""" Default key bindings.:: key_bindings = load_key_bindings() app = Application(key_bindings=key_bindings) """ from __future__ import unicode_literals from prompt_toolkit.filters import buffer_has_focus from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings from prompt_toolkit.key_binding.bindings.cpr import load_cpr_bindings from prompt_toolkit.key_binding.bindings.emacs import ( load_emacs_bindings, load_emacs_search_bindings, ) from prompt_toolkit.key_binding.bindings.mouse import load_mouse_bindings from prompt_toolkit.key_binding.bindings.vi import ( load_vi_bindings, load_vi_search_bindings, ) from prompt_toolkit.key_binding.key_bindings import ( ConditionalKeyBindings, merge_key_bindings, ) __all__ = [ 'load_key_bindings', ] def load_key_bindings(): """ Create a KeyBindings object that contains the default key bindings. """ all_bindings = merge_key_bindings([ # Load basic bindings. load_basic_bindings(), # Load emacs bindings. load_emacs_bindings(), load_emacs_search_bindings(), # Load Vi bindings. load_vi_bindings(), load_vi_search_bindings(), ]) return merge_key_bindings([ # Make sure that the above key bindings are only active if the # currently focused control is a `BufferControl`. For other controls, we # don't want these key bindings to intervene. (This would break "ptterm" # for instance, which handles 'Keys.Any' in the user control itself.) ConditionalKeyBindings(all_bindings, buffer_has_focus), # Active, even when no buffer has been focused. load_mouse_bindings(), load_cpr_bindings(), ]) prompt_toolkit-2.0.10/prompt_toolkit/key_binding/digraphs.py0000644000175100017510000010012213545407204026033 0ustar jonathanjonathan00000000000000# encoding: utf-8 from __future__ import unicode_literals """ Vi Digraphs. This is a list of special characters that can be inserted in Vi insert mode by pressing Control-K followed by to normal characters. Taken from Neovim and translated to Python: https://raw.githubusercontent.com/neovim/neovim/master/src/nvim/digraph.c """ __all__ = [ 'DIGRAPHS', ] # digraphs for Unicode from RFC1345 # (also work for ISO-8859-1 aka latin1) DIGRAPHS = { ('N', 'U'): 0x00, ('S', 'H'): 0x01, ('S', 'X'): 0x02, ('E', 'X'): 0x03, ('E', 'T'): 0x04, ('E', 'Q'): 0x05, ('A', 'K'): 0x06, ('B', 'L'): 0x07, ('B', 'S'): 0x08, ('H', 'T'): 0x09, ('L', 'F'): 0x0a, ('V', 'T'): 0x0b, ('F', 'F'): 0x0c, ('C', 'R'): 0x0d, ('S', 'O'): 0x0e, ('S', 'I'): 0x0f, ('D', 'L'): 0x10, ('D', '1'): 0x11, ('D', '2'): 0x12, ('D', '3'): 0x13, ('D', '4'): 0x14, ('N', 'K'): 0x15, ('S', 'Y'): 0x16, ('E', 'B'): 0x17, ('C', 'N'): 0x18, ('E', 'M'): 0x19, ('S', 'B'): 0x1a, ('E', 'C'): 0x1b, ('F', 'S'): 0x1c, ('G', 'S'): 0x1d, ('R', 'S'): 0x1e, ('U', 'S'): 0x1f, ('S', 'P'): 0x20, ('N', 'b'): 0x23, ('D', 'O'): 0x24, ('A', 't'): 0x40, ('<', '('): 0x5b, ('/', '/'): 0x5c, (')', '>'): 0x5d, ('\'', '>'): 0x5e, ('\'', '!'): 0x60, ('(', '!'): 0x7b, ('!', '!'): 0x7c, ('!', ')'): 0x7d, ('\'', '?'): 0x7e, ('D', 'T'): 0x7f, ('P', 'A'): 0x80, ('H', 'O'): 0x81, ('B', 'H'): 0x82, ('N', 'H'): 0x83, ('I', 'N'): 0x84, ('N', 'L'): 0x85, ('S', 'A'): 0x86, ('E', 'S'): 0x87, ('H', 'S'): 0x88, ('H', 'J'): 0x89, ('V', 'S'): 0x8a, ('P', 'D'): 0x8b, ('P', 'U'): 0x8c, ('R', 'I'): 0x8d, ('S', '2'): 0x8e, ('S', '3'): 0x8f, ('D', 'C'): 0x90, ('P', '1'): 0x91, ('P', '2'): 0x92, ('T', 'S'): 0x93, ('C', 'C'): 0x94, ('M', 'W'): 0x95, ('S', 'G'): 0x96, ('E', 'G'): 0x97, ('S', 'S'): 0x98, ('G', 'C'): 0x99, ('S', 'C'): 0x9a, ('C', 'I'): 0x9b, ('S', 'T'): 0x9c, ('O', 'C'): 0x9d, ('P', 'M'): 0x9e, ('A', 'C'): 0x9f, ('N', 'S'): 0xa0, ('!', 'I'): 0xa1, ('C', 't'): 0xa2, ('P', 'd'): 0xa3, ('C', 'u'): 0xa4, ('Y', 'e'): 0xa5, ('B', 'B'): 0xa6, ('S', 'E'): 0xa7, ('\'', ':'): 0xa8, ('C', 'o'): 0xa9, ('-', 'a'): 0xaa, ('<', '<'): 0xab, ('N', 'O'): 0xac, ('-', '-'): 0xad, ('R', 'g'): 0xae, ('\'', 'm'): 0xaf, ('D', 'G'): 0xb0, ('+', '-'): 0xb1, ('2', 'S'): 0xb2, ('3', 'S'): 0xb3, ('\'', '\''): 0xb4, ('M', 'y'): 0xb5, ('P', 'I'): 0xb6, ('.', 'M'): 0xb7, ('\'', ','): 0xb8, ('1', 'S'): 0xb9, ('-', 'o'): 0xba, ('>', '>'): 0xbb, ('1', '4'): 0xbc, ('1', '2'): 0xbd, ('3', '4'): 0xbe, ('?', 'I'): 0xbf, ('A', '!'): 0xc0, ('A', '\''): 0xc1, ('A', '>'): 0xc2, ('A', '?'): 0xc3, ('A', ':'): 0xc4, ('A', 'A'): 0xc5, ('A', 'E'): 0xc6, ('C', ','): 0xc7, ('E', '!'): 0xc8, ('E', '\''): 0xc9, ('E', '>'): 0xca, ('E', ':'): 0xcb, ('I', '!'): 0xcc, ('I', '\''): 0xcd, ('I', '>'): 0xce, ('I', ':'): 0xcf, ('D', '-'): 0xd0, ('N', '?'): 0xd1, ('O', '!'): 0xd2, ('O', '\''): 0xd3, ('O', '>'): 0xd4, ('O', '?'): 0xd5, ('O', ':'): 0xd6, ('*', 'X'): 0xd7, ('O', '/'): 0xd8, ('U', '!'): 0xd9, ('U', '\''): 0xda, ('U', '>'): 0xdb, ('U', ':'): 0xdc, ('Y', '\''): 0xdd, ('T', 'H'): 0xde, ('s', 's'): 0xdf, ('a', '!'): 0xe0, ('a', '\''): 0xe1, ('a', '>'): 0xe2, ('a', '?'): 0xe3, ('a', ':'): 0xe4, ('a', 'a'): 0xe5, ('a', 'e'): 0xe6, ('c', ','): 0xe7, ('e', '!'): 0xe8, ('e', '\''): 0xe9, ('e', '>'): 0xea, ('e', ':'): 0xeb, ('i', '!'): 0xec, ('i', '\''): 0xed, ('i', '>'): 0xee, ('i', ':'): 0xef, ('d', '-'): 0xf0, ('n', '?'): 0xf1, ('o', '!'): 0xf2, ('o', '\''): 0xf3, ('o', '>'): 0xf4, ('o', '?'): 0xf5, ('o', ':'): 0xf6, ('-', ':'): 0xf7, ('o', '/'): 0xf8, ('u', '!'): 0xf9, ('u', '\''): 0xfa, ('u', '>'): 0xfb, ('u', ':'): 0xfc, ('y', '\''): 0xfd, ('t', 'h'): 0xfe, ('y', ':'): 0xff, ('A', '-'): 0x0100, ('a', '-'): 0x0101, ('A', '('): 0x0102, ('a', '('): 0x0103, ('A', ';'): 0x0104, ('a', ';'): 0x0105, ('C', '\''): 0x0106, ('c', '\''): 0x0107, ('C', '>'): 0x0108, ('c', '>'): 0x0109, ('C', '.'): 0x010a, ('c', '.'): 0x010b, ('C', '<'): 0x010c, ('c', '<'): 0x010d, ('D', '<'): 0x010e, ('d', '<'): 0x010f, ('D', '/'): 0x0110, ('d', '/'): 0x0111, ('E', '-'): 0x0112, ('e', '-'): 0x0113, ('E', '('): 0x0114, ('e', '('): 0x0115, ('E', '.'): 0x0116, ('e', '.'): 0x0117, ('E', ';'): 0x0118, ('e', ';'): 0x0119, ('E', '<'): 0x011a, ('e', '<'): 0x011b, ('G', '>'): 0x011c, ('g', '>'): 0x011d, ('G', '('): 0x011e, ('g', '('): 0x011f, ('G', '.'): 0x0120, ('g', '.'): 0x0121, ('G', ','): 0x0122, ('g', ','): 0x0123, ('H', '>'): 0x0124, ('h', '>'): 0x0125, ('H', '/'): 0x0126, ('h', '/'): 0x0127, ('I', '?'): 0x0128, ('i', '?'): 0x0129, ('I', '-'): 0x012a, ('i', '-'): 0x012b, ('I', '('): 0x012c, ('i', '('): 0x012d, ('I', ';'): 0x012e, ('i', ';'): 0x012f, ('I', '.'): 0x0130, ('i', '.'): 0x0131, ('I', 'J'): 0x0132, ('i', 'j'): 0x0133, ('J', '>'): 0x0134, ('j', '>'): 0x0135, ('K', ','): 0x0136, ('k', ','): 0x0137, ('k', 'k'): 0x0138, ('L', '\''): 0x0139, ('l', '\''): 0x013a, ('L', ','): 0x013b, ('l', ','): 0x013c, ('L', '<'): 0x013d, ('l', '<'): 0x013e, ('L', '.'): 0x013f, ('l', '.'): 0x0140, ('L', '/'): 0x0141, ('l', '/'): 0x0142, ('N', '\''): 0x0143, ('n', '\''): 0x0144, ('N', ','): 0x0145, ('n', ','): 0x0146, ('N', '<'): 0x0147, ('n', '<'): 0x0148, ('\'', 'n'): 0x0149, ('N', 'G'): 0x014a, ('n', 'g'): 0x014b, ('O', '-'): 0x014c, ('o', '-'): 0x014d, ('O', '('): 0x014e, ('o', '('): 0x014f, ('O', '"'): 0x0150, ('o', '"'): 0x0151, ('O', 'E'): 0x0152, ('o', 'e'): 0x0153, ('R', '\''): 0x0154, ('r', '\''): 0x0155, ('R', ','): 0x0156, ('r', ','): 0x0157, ('R', '<'): 0x0158, ('r', '<'): 0x0159, ('S', '\''): 0x015a, ('s', '\''): 0x015b, ('S', '>'): 0x015c, ('s', '>'): 0x015d, ('S', ','): 0x015e, ('s', ','): 0x015f, ('S', '<'): 0x0160, ('s', '<'): 0x0161, ('T', ','): 0x0162, ('t', ','): 0x0163, ('T', '<'): 0x0164, ('t', '<'): 0x0165, ('T', '/'): 0x0166, ('t', '/'): 0x0167, ('U', '?'): 0x0168, ('u', '?'): 0x0169, ('U', '-'): 0x016a, ('u', '-'): 0x016b, ('U', '('): 0x016c, ('u', '('): 0x016d, ('U', '0'): 0x016e, ('u', '0'): 0x016f, ('U', '"'): 0x0170, ('u', '"'): 0x0171, ('U', ';'): 0x0172, ('u', ';'): 0x0173, ('W', '>'): 0x0174, ('w', '>'): 0x0175, ('Y', '>'): 0x0176, ('y', '>'): 0x0177, ('Y', ':'): 0x0178, ('Z', '\''): 0x0179, ('z', '\''): 0x017a, ('Z', '.'): 0x017b, ('z', '.'): 0x017c, ('Z', '<'): 0x017d, ('z', '<'): 0x017e, ('O', '9'): 0x01a0, ('o', '9'): 0x01a1, ('O', 'I'): 0x01a2, ('o', 'i'): 0x01a3, ('y', 'r'): 0x01a6, ('U', '9'): 0x01af, ('u', '9'): 0x01b0, ('Z', '/'): 0x01b5, ('z', '/'): 0x01b6, ('E', 'D'): 0x01b7, ('A', '<'): 0x01cd, ('a', '<'): 0x01ce, ('I', '<'): 0x01cf, ('i', '<'): 0x01d0, ('O', '<'): 0x01d1, ('o', '<'): 0x01d2, ('U', '<'): 0x01d3, ('u', '<'): 0x01d4, ('A', '1'): 0x01de, ('a', '1'): 0x01df, ('A', '7'): 0x01e0, ('a', '7'): 0x01e1, ('A', '3'): 0x01e2, ('a', '3'): 0x01e3, ('G', '/'): 0x01e4, ('g', '/'): 0x01e5, ('G', '<'): 0x01e6, ('g', '<'): 0x01e7, ('K', '<'): 0x01e8, ('k', '<'): 0x01e9, ('O', ';'): 0x01ea, ('o', ';'): 0x01eb, ('O', '1'): 0x01ec, ('o', '1'): 0x01ed, ('E', 'Z'): 0x01ee, ('e', 'z'): 0x01ef, ('j', '<'): 0x01f0, ('G', '\''): 0x01f4, ('g', '\''): 0x01f5, (';', 'S'): 0x02bf, ('\'', '<'): 0x02c7, ('\'', '('): 0x02d8, ('\'', '.'): 0x02d9, ('\'', '0'): 0x02da, ('\'', ';'): 0x02db, ('\'', '"'): 0x02dd, ('A', '%'): 0x0386, ('E', '%'): 0x0388, ('Y', '%'): 0x0389, ('I', '%'): 0x038a, ('O', '%'): 0x038c, ('U', '%'): 0x038e, ('W', '%'): 0x038f, ('i', '3'): 0x0390, ('A', '*'): 0x0391, ('B', '*'): 0x0392, ('G', '*'): 0x0393, ('D', '*'): 0x0394, ('E', '*'): 0x0395, ('Z', '*'): 0x0396, ('Y', '*'): 0x0397, ('H', '*'): 0x0398, ('I', '*'): 0x0399, ('K', '*'): 0x039a, ('L', '*'): 0x039b, ('M', '*'): 0x039c, ('N', '*'): 0x039d, ('C', '*'): 0x039e, ('O', '*'): 0x039f, ('P', '*'): 0x03a0, ('R', '*'): 0x03a1, ('S', '*'): 0x03a3, ('T', '*'): 0x03a4, ('U', '*'): 0x03a5, ('F', '*'): 0x03a6, ('X', '*'): 0x03a7, ('Q', '*'): 0x03a8, ('W', '*'): 0x03a9, ('J', '*'): 0x03aa, ('V', '*'): 0x03ab, ('a', '%'): 0x03ac, ('e', '%'): 0x03ad, ('y', '%'): 0x03ae, ('i', '%'): 0x03af, ('u', '3'): 0x03b0, ('a', '*'): 0x03b1, ('b', '*'): 0x03b2, ('g', '*'): 0x03b3, ('d', '*'): 0x03b4, ('e', '*'): 0x03b5, ('z', '*'): 0x03b6, ('y', '*'): 0x03b7, ('h', '*'): 0x03b8, ('i', '*'): 0x03b9, ('k', '*'): 0x03ba, ('l', '*'): 0x03bb, ('m', '*'): 0x03bc, ('n', '*'): 0x03bd, ('c', '*'): 0x03be, ('o', '*'): 0x03bf, ('p', '*'): 0x03c0, ('r', '*'): 0x03c1, ('*', 's'): 0x03c2, ('s', '*'): 0x03c3, ('t', '*'): 0x03c4, ('u', '*'): 0x03c5, ('f', '*'): 0x03c6, ('x', '*'): 0x03c7, ('q', '*'): 0x03c8, ('w', '*'): 0x03c9, ('j', '*'): 0x03ca, ('v', '*'): 0x03cb, ('o', '%'): 0x03cc, ('u', '%'): 0x03cd, ('w', '%'): 0x03ce, ('\'', 'G'): 0x03d8, (',', 'G'): 0x03d9, ('T', '3'): 0x03da, ('t', '3'): 0x03db, ('M', '3'): 0x03dc, ('m', '3'): 0x03dd, ('K', '3'): 0x03de, ('k', '3'): 0x03df, ('P', '3'): 0x03e0, ('p', '3'): 0x03e1, ('\'', '%'): 0x03f4, ('j', '3'): 0x03f5, ('I', 'O'): 0x0401, ('D', '%'): 0x0402, ('G', '%'): 0x0403, ('I', 'E'): 0x0404, ('D', 'S'): 0x0405, ('I', 'I'): 0x0406, ('Y', 'I'): 0x0407, ('J', '%'): 0x0408, ('L', 'J'): 0x0409, ('N', 'J'): 0x040a, ('T', 's'): 0x040b, ('K', 'J'): 0x040c, ('V', '%'): 0x040e, ('D', 'Z'): 0x040f, ('A', '='): 0x0410, ('B', '='): 0x0411, ('V', '='): 0x0412, ('G', '='): 0x0413, ('D', '='): 0x0414, ('E', '='): 0x0415, ('Z', '%'): 0x0416, ('Z', '='): 0x0417, ('I', '='): 0x0418, ('J', '='): 0x0419, ('K', '='): 0x041a, ('L', '='): 0x041b, ('M', '='): 0x041c, ('N', '='): 0x041d, ('O', '='): 0x041e, ('P', '='): 0x041f, ('R', '='): 0x0420, ('S', '='): 0x0421, ('T', '='): 0x0422, ('U', '='): 0x0423, ('F', '='): 0x0424, ('H', '='): 0x0425, ('C', '='): 0x0426, ('C', '%'): 0x0427, ('S', '%'): 0x0428, ('S', 'c'): 0x0429, ('=', '"'): 0x042a, ('Y', '='): 0x042b, ('%', '"'): 0x042c, ('J', 'E'): 0x042d, ('J', 'U'): 0x042e, ('J', 'A'): 0x042f, ('a', '='): 0x0430, ('b', '='): 0x0431, ('v', '='): 0x0432, ('g', '='): 0x0433, ('d', '='): 0x0434, ('e', '='): 0x0435, ('z', '%'): 0x0436, ('z', '='): 0x0437, ('i', '='): 0x0438, ('j', '='): 0x0439, ('k', '='): 0x043a, ('l', '='): 0x043b, ('m', '='): 0x043c, ('n', '='): 0x043d, ('o', '='): 0x043e, ('p', '='): 0x043f, ('r', '='): 0x0440, ('s', '='): 0x0441, ('t', '='): 0x0442, ('u', '='): 0x0443, ('f', '='): 0x0444, ('h', '='): 0x0445, ('c', '='): 0x0446, ('c', '%'): 0x0447, ('s', '%'): 0x0448, ('s', 'c'): 0x0449, ('=', '\''): 0x044a, ('y', '='): 0x044b, ('%', '\''): 0x044c, ('j', 'e'): 0x044d, ('j', 'u'): 0x044e, ('j', 'a'): 0x044f, ('i', 'o'): 0x0451, ('d', '%'): 0x0452, ('g', '%'): 0x0453, ('i', 'e'): 0x0454, ('d', 's'): 0x0455, ('i', 'i'): 0x0456, ('y', 'i'): 0x0457, ('j', '%'): 0x0458, ('l', 'j'): 0x0459, ('n', 'j'): 0x045a, ('t', 's'): 0x045b, ('k', 'j'): 0x045c, ('v', '%'): 0x045e, ('d', 'z'): 0x045f, ('Y', '3'): 0x0462, ('y', '3'): 0x0463, ('O', '3'): 0x046a, ('o', '3'): 0x046b, ('F', '3'): 0x0472, ('f', '3'): 0x0473, ('V', '3'): 0x0474, ('v', '3'): 0x0475, ('C', '3'): 0x0480, ('c', '3'): 0x0481, ('G', '3'): 0x0490, ('g', '3'): 0x0491, ('A', '+'): 0x05d0, ('B', '+'): 0x05d1, ('G', '+'): 0x05d2, ('D', '+'): 0x05d3, ('H', '+'): 0x05d4, ('W', '+'): 0x05d5, ('Z', '+'): 0x05d6, ('X', '+'): 0x05d7, ('T', 'j'): 0x05d8, ('J', '+'): 0x05d9, ('K', '%'): 0x05da, ('K', '+'): 0x05db, ('L', '+'): 0x05dc, ('M', '%'): 0x05dd, ('M', '+'): 0x05de, ('N', '%'): 0x05df, ('N', '+'): 0x05e0, ('S', '+'): 0x05e1, ('E', '+'): 0x05e2, ('P', '%'): 0x05e3, ('P', '+'): 0x05e4, ('Z', 'j'): 0x05e5, ('Z', 'J'): 0x05e6, ('Q', '+'): 0x05e7, ('R', '+'): 0x05e8, ('S', 'h'): 0x05e9, ('T', '+'): 0x05ea, (',', '+'): 0x060c, (';', '+'): 0x061b, ('?', '+'): 0x061f, ('H', '\''): 0x0621, ('a', 'M'): 0x0622, ('a', 'H'): 0x0623, ('w', 'H'): 0x0624, ('a', 'h'): 0x0625, ('y', 'H'): 0x0626, ('a', '+'): 0x0627, ('b', '+'): 0x0628, ('t', 'm'): 0x0629, ('t', '+'): 0x062a, ('t', 'k'): 0x062b, ('g', '+'): 0x062c, ('h', 'k'): 0x062d, ('x', '+'): 0x062e, ('d', '+'): 0x062f, ('d', 'k'): 0x0630, ('r', '+'): 0x0631, ('z', '+'): 0x0632, ('s', '+'): 0x0633, ('s', 'n'): 0x0634, ('c', '+'): 0x0635, ('d', 'd'): 0x0636, ('t', 'j'): 0x0637, ('z', 'H'): 0x0638, ('e', '+'): 0x0639, ('i', '+'): 0x063a, ('+', '+'): 0x0640, ('f', '+'): 0x0641, ('q', '+'): 0x0642, ('k', '+'): 0x0643, ('l', '+'): 0x0644, ('m', '+'): 0x0645, ('n', '+'): 0x0646, ('h', '+'): 0x0647, ('w', '+'): 0x0648, ('j', '+'): 0x0649, ('y', '+'): 0x064a, (':', '+'): 0x064b, ('"', '+'): 0x064c, ('=', '+'): 0x064d, ('/', '+'): 0x064e, ('\'', '+'): 0x064f, ('1', '+'): 0x0650, ('3', '+'): 0x0651, ('0', '+'): 0x0652, ('a', 'S'): 0x0670, ('p', '+'): 0x067e, ('v', '+'): 0x06a4, ('g', 'f'): 0x06af, ('0', 'a'): 0x06f0, ('1', 'a'): 0x06f1, ('2', 'a'): 0x06f2, ('3', 'a'): 0x06f3, ('4', 'a'): 0x06f4, ('5', 'a'): 0x06f5, ('6', 'a'): 0x06f6, ('7', 'a'): 0x06f7, ('8', 'a'): 0x06f8, ('9', 'a'): 0x06f9, ('B', '.'): 0x1e02, ('b', '.'): 0x1e03, ('B', '_'): 0x1e06, ('b', '_'): 0x1e07, ('D', '.'): 0x1e0a, ('d', '.'): 0x1e0b, ('D', '_'): 0x1e0e, ('d', '_'): 0x1e0f, ('D', ','): 0x1e10, ('d', ','): 0x1e11, ('F', '.'): 0x1e1e, ('f', '.'): 0x1e1f, ('G', '-'): 0x1e20, ('g', '-'): 0x1e21, ('H', '.'): 0x1e22, ('h', '.'): 0x1e23, ('H', ':'): 0x1e26, ('h', ':'): 0x1e27, ('H', ','): 0x1e28, ('h', ','): 0x1e29, ('K', '\''): 0x1e30, ('k', '\''): 0x1e31, ('K', '_'): 0x1e34, ('k', '_'): 0x1e35, ('L', '_'): 0x1e3a, ('l', '_'): 0x1e3b, ('M', '\''): 0x1e3e, ('m', '\''): 0x1e3f, ('M', '.'): 0x1e40, ('m', '.'): 0x1e41, ('N', '.'): 0x1e44, ('n', '.'): 0x1e45, ('N', '_'): 0x1e48, ('n', '_'): 0x1e49, ('P', '\''): 0x1e54, ('p', '\''): 0x1e55, ('P', '.'): 0x1e56, ('p', '.'): 0x1e57, ('R', '.'): 0x1e58, ('r', '.'): 0x1e59, ('R', '_'): 0x1e5e, ('r', '_'): 0x1e5f, ('S', '.'): 0x1e60, ('s', '.'): 0x1e61, ('T', '.'): 0x1e6a, ('t', '.'): 0x1e6b, ('T', '_'): 0x1e6e, ('t', '_'): 0x1e6f, ('V', '?'): 0x1e7c, ('v', '?'): 0x1e7d, ('W', '!'): 0x1e80, ('w', '!'): 0x1e81, ('W', '\''): 0x1e82, ('w', '\''): 0x1e83, ('W', ':'): 0x1e84, ('w', ':'): 0x1e85, ('W', '.'): 0x1e86, ('w', '.'): 0x1e87, ('X', '.'): 0x1e8a, ('x', '.'): 0x1e8b, ('X', ':'): 0x1e8c, ('x', ':'): 0x1e8d, ('Y', '.'): 0x1e8e, ('y', '.'): 0x1e8f, ('Z', '>'): 0x1e90, ('z', '>'): 0x1e91, ('Z', '_'): 0x1e94, ('z', '_'): 0x1e95, ('h', '_'): 0x1e96, ('t', ':'): 0x1e97, ('w', '0'): 0x1e98, ('y', '0'): 0x1e99, ('A', '2'): 0x1ea2, ('a', '2'): 0x1ea3, ('E', '2'): 0x1eba, ('e', '2'): 0x1ebb, ('E', '?'): 0x1ebc, ('e', '?'): 0x1ebd, ('I', '2'): 0x1ec8, ('i', '2'): 0x1ec9, ('O', '2'): 0x1ece, ('o', '2'): 0x1ecf, ('U', '2'): 0x1ee6, ('u', '2'): 0x1ee7, ('Y', '!'): 0x1ef2, ('y', '!'): 0x1ef3, ('Y', '2'): 0x1ef6, ('y', '2'): 0x1ef7, ('Y', '?'): 0x1ef8, ('y', '?'): 0x1ef9, (';', '\''): 0x1f00, (',', '\''): 0x1f01, (';', '!'): 0x1f02, (',', '!'): 0x1f03, ('?', ';'): 0x1f04, ('?', ','): 0x1f05, ('!', ':'): 0x1f06, ('?', ':'): 0x1f07, ('1', 'N'): 0x2002, ('1', 'M'): 0x2003, ('3', 'M'): 0x2004, ('4', 'M'): 0x2005, ('6', 'M'): 0x2006, ('1', 'T'): 0x2009, ('1', 'H'): 0x200a, ('-', '1'): 0x2010, ('-', 'N'): 0x2013, ('-', 'M'): 0x2014, ('-', '3'): 0x2015, ('!', '2'): 0x2016, ('=', '2'): 0x2017, ('\'', '6'): 0x2018, ('\'', '9'): 0x2019, ('.', '9'): 0x201a, ('9', '\''): 0x201b, ('"', '6'): 0x201c, ('"', '9'): 0x201d, (':', '9'): 0x201e, ('9', '"'): 0x201f, ('/', '-'): 0x2020, ('/', '='): 0x2021, ('.', '.'): 0x2025, ('%', '0'): 0x2030, ('1', '\''): 0x2032, ('2', '\''): 0x2033, ('3', '\''): 0x2034, ('1', '"'): 0x2035, ('2', '"'): 0x2036, ('3', '"'): 0x2037, ('C', 'a'): 0x2038, ('<', '1'): 0x2039, ('>', '1'): 0x203a, (':', 'X'): 0x203b, ('\'', '-'): 0x203e, ('/', 'f'): 0x2044, ('0', 'S'): 0x2070, ('4', 'S'): 0x2074, ('5', 'S'): 0x2075, ('6', 'S'): 0x2076, ('7', 'S'): 0x2077, ('8', 'S'): 0x2078, ('9', 'S'): 0x2079, ('+', 'S'): 0x207a, ('-', 'S'): 0x207b, ('=', 'S'): 0x207c, ('(', 'S'): 0x207d, (')', 'S'): 0x207e, ('n', 'S'): 0x207f, ('0', 's'): 0x2080, ('1', 's'): 0x2081, ('2', 's'): 0x2082, ('3', 's'): 0x2083, ('4', 's'): 0x2084, ('5', 's'): 0x2085, ('6', 's'): 0x2086, ('7', 's'): 0x2087, ('8', 's'): 0x2088, ('9', 's'): 0x2089, ('+', 's'): 0x208a, ('-', 's'): 0x208b, ('=', 's'): 0x208c, ('(', 's'): 0x208d, (')', 's'): 0x208e, ('L', 'i'): 0x20a4, ('P', 't'): 0x20a7, ('W', '='): 0x20a9, ('=', 'e'): 0x20ac, # euro ('E', 'u'): 0x20ac, # euro ('=', 'R'): 0x20bd, # rouble ('=', 'P'): 0x20bd, # rouble ('o', 'C'): 0x2103, ('c', 'o'): 0x2105, ('o', 'F'): 0x2109, ('N', '0'): 0x2116, ('P', 'O'): 0x2117, ('R', 'x'): 0x211e, ('S', 'M'): 0x2120, ('T', 'M'): 0x2122, ('O', 'm'): 0x2126, ('A', 'O'): 0x212b, ('1', '3'): 0x2153, ('2', '3'): 0x2154, ('1', '5'): 0x2155, ('2', '5'): 0x2156, ('3', '5'): 0x2157, ('4', '5'): 0x2158, ('1', '6'): 0x2159, ('5', '6'): 0x215a, ('1', '8'): 0x215b, ('3', '8'): 0x215c, ('5', '8'): 0x215d, ('7', '8'): 0x215e, ('1', 'R'): 0x2160, ('2', 'R'): 0x2161, ('3', 'R'): 0x2162, ('4', 'R'): 0x2163, ('5', 'R'): 0x2164, ('6', 'R'): 0x2165, ('7', 'R'): 0x2166, ('8', 'R'): 0x2167, ('9', 'R'): 0x2168, ('a', 'R'): 0x2169, ('b', 'R'): 0x216a, ('c', 'R'): 0x216b, ('1', 'r'): 0x2170, ('2', 'r'): 0x2171, ('3', 'r'): 0x2172, ('4', 'r'): 0x2173, ('5', 'r'): 0x2174, ('6', 'r'): 0x2175, ('7', 'r'): 0x2176, ('8', 'r'): 0x2177, ('9', 'r'): 0x2178, ('a', 'r'): 0x2179, ('b', 'r'): 0x217a, ('c', 'r'): 0x217b, ('<', '-'): 0x2190, ('-', '!'): 0x2191, ('-', '>'): 0x2192, ('-', 'v'): 0x2193, ('<', '>'): 0x2194, ('U', 'D'): 0x2195, ('<', '='): 0x21d0, ('=', '>'): 0x21d2, ('=', '='): 0x21d4, ('F', 'A'): 0x2200, ('d', 'P'): 0x2202, ('T', 'E'): 0x2203, ('/', '0'): 0x2205, ('D', 'E'): 0x2206, ('N', 'B'): 0x2207, ('(', '-'): 0x2208, ('-', ')'): 0x220b, ('*', 'P'): 0x220f, ('+', 'Z'): 0x2211, ('-', '2'): 0x2212, ('-', '+'): 0x2213, ('*', '-'): 0x2217, ('O', 'b'): 0x2218, ('S', 'b'): 0x2219, ('R', 'T'): 0x221a, ('0', '('): 0x221d, ('0', '0'): 0x221e, ('-', 'L'): 0x221f, ('-', 'V'): 0x2220, ('P', 'P'): 0x2225, ('A', 'N'): 0x2227, ('O', 'R'): 0x2228, ('(', 'U'): 0x2229, (')', 'U'): 0x222a, ('I', 'n'): 0x222b, ('D', 'I'): 0x222c, ('I', 'o'): 0x222e, ('.', ':'): 0x2234, (':', '.'): 0x2235, (':', 'R'): 0x2236, (':', ':'): 0x2237, ('?', '1'): 0x223c, ('C', 'G'): 0x223e, ('?', '-'): 0x2243, ('?', '='): 0x2245, ('?', '2'): 0x2248, ('=', '?'): 0x224c, ('H', 'I'): 0x2253, ('!', '='): 0x2260, ('=', '3'): 0x2261, ('=', '<'): 0x2264, ('>', '='): 0x2265, ('<', '*'): 0x226a, ('*', '>'): 0x226b, ('!', '<'): 0x226e, ('!', '>'): 0x226f, ('(', 'C'): 0x2282, (')', 'C'): 0x2283, ('(', '_'): 0x2286, (')', '_'): 0x2287, ('0', '.'): 0x2299, ('0', '2'): 0x229a, ('-', 'T'): 0x22a5, ('.', 'P'): 0x22c5, (':', '3'): 0x22ee, ('.', '3'): 0x22ef, ('E', 'h'): 0x2302, ('<', '7'): 0x2308, ('>', '7'): 0x2309, ('7', '<'): 0x230a, ('7', '>'): 0x230b, ('N', 'I'): 0x2310, ('(', 'A'): 0x2312, ('T', 'R'): 0x2315, ('I', 'u'): 0x2320, ('I', 'l'): 0x2321, ('<', '/'): 0x2329, ('/', '>'): 0x232a, ('V', 's'): 0x2423, ('1', 'h'): 0x2440, ('3', 'h'): 0x2441, ('2', 'h'): 0x2442, ('4', 'h'): 0x2443, ('1', 'j'): 0x2446, ('2', 'j'): 0x2447, ('3', 'j'): 0x2448, ('4', 'j'): 0x2449, ('1', '.'): 0x2488, ('2', '.'): 0x2489, ('3', '.'): 0x248a, ('4', '.'): 0x248b, ('5', '.'): 0x248c, ('6', '.'): 0x248d, ('7', '.'): 0x248e, ('8', '.'): 0x248f, ('9', '.'): 0x2490, ('h', 'h'): 0x2500, ('H', 'H'): 0x2501, ('v', 'v'): 0x2502, ('V', 'V'): 0x2503, ('3', '-'): 0x2504, ('3', '_'): 0x2505, ('3', '!'): 0x2506, ('3', '/'): 0x2507, ('4', '-'): 0x2508, ('4', '_'): 0x2509, ('4', '!'): 0x250a, ('4', '/'): 0x250b, ('d', 'r'): 0x250c, ('d', 'R'): 0x250d, ('D', 'r'): 0x250e, ('D', 'R'): 0x250f, ('d', 'l'): 0x2510, ('d', 'L'): 0x2511, ('D', 'l'): 0x2512, ('L', 'D'): 0x2513, ('u', 'r'): 0x2514, ('u', 'R'): 0x2515, ('U', 'r'): 0x2516, ('U', 'R'): 0x2517, ('u', 'l'): 0x2518, ('u', 'L'): 0x2519, ('U', 'l'): 0x251a, ('U', 'L'): 0x251b, ('v', 'r'): 0x251c, ('v', 'R'): 0x251d, ('V', 'r'): 0x2520, ('V', 'R'): 0x2523, ('v', 'l'): 0x2524, ('v', 'L'): 0x2525, ('V', 'l'): 0x2528, ('V', 'L'): 0x252b, ('d', 'h'): 0x252c, ('d', 'H'): 0x252f, ('D', 'h'): 0x2530, ('D', 'H'): 0x2533, ('u', 'h'): 0x2534, ('u', 'H'): 0x2537, ('U', 'h'): 0x2538, ('U', 'H'): 0x253b, ('v', 'h'): 0x253c, ('v', 'H'): 0x253f, ('V', 'h'): 0x2542, ('V', 'H'): 0x254b, ('F', 'D'): 0x2571, ('B', 'D'): 0x2572, ('T', 'B'): 0x2580, ('L', 'B'): 0x2584, ('F', 'B'): 0x2588, ('l', 'B'): 0x258c, ('R', 'B'): 0x2590, ('.', 'S'): 0x2591, (':', 'S'): 0x2592, ('?', 'S'): 0x2593, ('f', 'S'): 0x25a0, ('O', 'S'): 0x25a1, ('R', 'O'): 0x25a2, ('R', 'r'): 0x25a3, ('R', 'F'): 0x25a4, ('R', 'Y'): 0x25a5, ('R', 'H'): 0x25a6, ('R', 'Z'): 0x25a7, ('R', 'K'): 0x25a8, ('R', 'X'): 0x25a9, ('s', 'B'): 0x25aa, ('S', 'R'): 0x25ac, ('O', 'r'): 0x25ad, ('U', 'T'): 0x25b2, ('u', 'T'): 0x25b3, ('P', 'R'): 0x25b6, ('T', 'r'): 0x25b7, ('D', 't'): 0x25bc, ('d', 'T'): 0x25bd, ('P', 'L'): 0x25c0, ('T', 'l'): 0x25c1, ('D', 'b'): 0x25c6, ('D', 'w'): 0x25c7, ('L', 'Z'): 0x25ca, ('0', 'm'): 0x25cb, ('0', 'o'): 0x25ce, ('0', 'M'): 0x25cf, ('0', 'L'): 0x25d0, ('0', 'R'): 0x25d1, ('S', 'n'): 0x25d8, ('I', 'c'): 0x25d9, ('F', 'd'): 0x25e2, ('B', 'd'): 0x25e3, ('*', '2'): 0x2605, ('*', '1'): 0x2606, ('<', 'H'): 0x261c, ('>', 'H'): 0x261e, ('0', 'u'): 0x263a, ('0', 'U'): 0x263b, ('S', 'U'): 0x263c, ('F', 'm'): 0x2640, ('M', 'l'): 0x2642, ('c', 'S'): 0x2660, ('c', 'H'): 0x2661, ('c', 'D'): 0x2662, ('c', 'C'): 0x2663, ('M', 'd'): 0x2669, ('M', '8'): 0x266a, ('M', '2'): 0x266b, ('M', 'b'): 0x266d, ('M', 'x'): 0x266e, ('M', 'X'): 0x266f, ('O', 'K'): 0x2713, ('X', 'X'): 0x2717, ('-', 'X'): 0x2720, ('I', 'S'): 0x3000, (',', '_'): 0x3001, ('.', '_'): 0x3002, ('+', '"'): 0x3003, ('+', '_'): 0x3004, ('*', '_'): 0x3005, (';', '_'): 0x3006, ('0', '_'): 0x3007, ('<', '+'): 0x300a, ('>', '+'): 0x300b, ('<', '\''): 0x300c, ('>', '\''): 0x300d, ('<', '"'): 0x300e, ('>', '"'): 0x300f, ('(', '"'): 0x3010, (')', '"'): 0x3011, ('=', 'T'): 0x3012, ('=', '_'): 0x3013, ('(', '\''): 0x3014, (')', '\''): 0x3015, ('(', 'I'): 0x3016, (')', 'I'): 0x3017, ('-', '?'): 0x301c, ('A', '5'): 0x3041, ('a', '5'): 0x3042, ('I', '5'): 0x3043, ('i', '5'): 0x3044, ('U', '5'): 0x3045, ('u', '5'): 0x3046, ('E', '5'): 0x3047, ('e', '5'): 0x3048, ('O', '5'): 0x3049, ('o', '5'): 0x304a, ('k', 'a'): 0x304b, ('g', 'a'): 0x304c, ('k', 'i'): 0x304d, ('g', 'i'): 0x304e, ('k', 'u'): 0x304f, ('g', 'u'): 0x3050, ('k', 'e'): 0x3051, ('g', 'e'): 0x3052, ('k', 'o'): 0x3053, ('g', 'o'): 0x3054, ('s', 'a'): 0x3055, ('z', 'a'): 0x3056, ('s', 'i'): 0x3057, ('z', 'i'): 0x3058, ('s', 'u'): 0x3059, ('z', 'u'): 0x305a, ('s', 'e'): 0x305b, ('z', 'e'): 0x305c, ('s', 'o'): 0x305d, ('z', 'o'): 0x305e, ('t', 'a'): 0x305f, ('d', 'a'): 0x3060, ('t', 'i'): 0x3061, ('d', 'i'): 0x3062, ('t', 'U'): 0x3063, ('t', 'u'): 0x3064, ('d', 'u'): 0x3065, ('t', 'e'): 0x3066, ('d', 'e'): 0x3067, ('t', 'o'): 0x3068, ('d', 'o'): 0x3069, ('n', 'a'): 0x306a, ('n', 'i'): 0x306b, ('n', 'u'): 0x306c, ('n', 'e'): 0x306d, ('n', 'o'): 0x306e, ('h', 'a'): 0x306f, ('b', 'a'): 0x3070, ('p', 'a'): 0x3071, ('h', 'i'): 0x3072, ('b', 'i'): 0x3073, ('p', 'i'): 0x3074, ('h', 'u'): 0x3075, ('b', 'u'): 0x3076, ('p', 'u'): 0x3077, ('h', 'e'): 0x3078, ('b', 'e'): 0x3079, ('p', 'e'): 0x307a, ('h', 'o'): 0x307b, ('b', 'o'): 0x307c, ('p', 'o'): 0x307d, ('m', 'a'): 0x307e, ('m', 'i'): 0x307f, ('m', 'u'): 0x3080, ('m', 'e'): 0x3081, ('m', 'o'): 0x3082, ('y', 'A'): 0x3083, ('y', 'a'): 0x3084, ('y', 'U'): 0x3085, ('y', 'u'): 0x3086, ('y', 'O'): 0x3087, ('y', 'o'): 0x3088, ('r', 'a'): 0x3089, ('r', 'i'): 0x308a, ('r', 'u'): 0x308b, ('r', 'e'): 0x308c, ('r', 'o'): 0x308d, ('w', 'A'): 0x308e, ('w', 'a'): 0x308f, ('w', 'i'): 0x3090, ('w', 'e'): 0x3091, ('w', 'o'): 0x3092, ('n', '5'): 0x3093, ('v', 'u'): 0x3094, ('"', '5'): 0x309b, ('0', '5'): 0x309c, ('*', '5'): 0x309d, ('+', '5'): 0x309e, ('a', '6'): 0x30a1, ('A', '6'): 0x30a2, ('i', '6'): 0x30a3, ('I', '6'): 0x30a4, ('u', '6'): 0x30a5, ('U', '6'): 0x30a6, ('e', '6'): 0x30a7, ('E', '6'): 0x30a8, ('o', '6'): 0x30a9, ('O', '6'): 0x30aa, ('K', 'a'): 0x30ab, ('G', 'a'): 0x30ac, ('K', 'i'): 0x30ad, ('G', 'i'): 0x30ae, ('K', 'u'): 0x30af, ('G', 'u'): 0x30b0, ('K', 'e'): 0x30b1, ('G', 'e'): 0x30b2, ('K', 'o'): 0x30b3, ('G', 'o'): 0x30b4, ('S', 'a'): 0x30b5, ('Z', 'a'): 0x30b6, ('S', 'i'): 0x30b7, ('Z', 'i'): 0x30b8, ('S', 'u'): 0x30b9, ('Z', 'u'): 0x30ba, ('S', 'e'): 0x30bb, ('Z', 'e'): 0x30bc, ('S', 'o'): 0x30bd, ('Z', 'o'): 0x30be, ('T', 'a'): 0x30bf, ('D', 'a'): 0x30c0, ('T', 'i'): 0x30c1, ('D', 'i'): 0x30c2, ('T', 'U'): 0x30c3, ('T', 'u'): 0x30c4, ('D', 'u'): 0x30c5, ('T', 'e'): 0x30c6, ('D', 'e'): 0x30c7, ('T', 'o'): 0x30c8, ('D', 'o'): 0x30c9, ('N', 'a'): 0x30ca, ('N', 'i'): 0x30cb, ('N', 'u'): 0x30cc, ('N', 'e'): 0x30cd, ('N', 'o'): 0x30ce, ('H', 'a'): 0x30cf, ('B', 'a'): 0x30d0, ('P', 'a'): 0x30d1, ('H', 'i'): 0x30d2, ('B', 'i'): 0x30d3, ('P', 'i'): 0x30d4, ('H', 'u'): 0x30d5, ('B', 'u'): 0x30d6, ('P', 'u'): 0x30d7, ('H', 'e'): 0x30d8, ('B', 'e'): 0x30d9, ('P', 'e'): 0x30da, ('H', 'o'): 0x30db, ('B', 'o'): 0x30dc, ('P', 'o'): 0x30dd, ('M', 'a'): 0x30de, ('M', 'i'): 0x30df, ('M', 'u'): 0x30e0, ('M', 'e'): 0x30e1, ('M', 'o'): 0x30e2, ('Y', 'A'): 0x30e3, ('Y', 'a'): 0x30e4, ('Y', 'U'): 0x30e5, ('Y', 'u'): 0x30e6, ('Y', 'O'): 0x30e7, ('Y', 'o'): 0x30e8, ('R', 'a'): 0x30e9, ('R', 'i'): 0x30ea, ('R', 'u'): 0x30eb, ('R', 'e'): 0x30ec, ('R', 'o'): 0x30ed, ('W', 'A'): 0x30ee, ('W', 'a'): 0x30ef, ('W', 'i'): 0x30f0, ('W', 'e'): 0x30f1, ('W', 'o'): 0x30f2, ('N', '6'): 0x30f3, ('V', 'u'): 0x30f4, ('K', 'A'): 0x30f5, ('K', 'E'): 0x30f6, ('V', 'a'): 0x30f7, ('V', 'i'): 0x30f8, ('V', 'e'): 0x30f9, ('V', 'o'): 0x30fa, ('.', '6'): 0x30fb, ('-', '6'): 0x30fc, ('*', '6'): 0x30fd, ('+', '6'): 0x30fe, ('b', '4'): 0x3105, ('p', '4'): 0x3106, ('m', '4'): 0x3107, ('f', '4'): 0x3108, ('d', '4'): 0x3109, ('t', '4'): 0x310a, ('n', '4'): 0x310b, ('l', '4'): 0x310c, ('g', '4'): 0x310d, ('k', '4'): 0x310e, ('h', '4'): 0x310f, ('j', '4'): 0x3110, ('q', '4'): 0x3111, ('x', '4'): 0x3112, ('z', 'h'): 0x3113, ('c', 'h'): 0x3114, ('s', 'h'): 0x3115, ('r', '4'): 0x3116, ('z', '4'): 0x3117, ('c', '4'): 0x3118, ('s', '4'): 0x3119, ('a', '4'): 0x311a, ('o', '4'): 0x311b, ('e', '4'): 0x311c, ('a', 'i'): 0x311e, ('e', 'i'): 0x311f, ('a', 'u'): 0x3120, ('o', 'u'): 0x3121, ('a', 'n'): 0x3122, ('e', 'n'): 0x3123, ('a', 'N'): 0x3124, ('e', 'N'): 0x3125, ('e', 'r'): 0x3126, ('i', '4'): 0x3127, ('u', '4'): 0x3128, ('i', 'u'): 0x3129, ('v', '4'): 0x312a, ('n', 'G'): 0x312b, ('g', 'n'): 0x312c, ('1', 'c'): 0x3220, ('2', 'c'): 0x3221, ('3', 'c'): 0x3222, ('4', 'c'): 0x3223, ('5', 'c'): 0x3224, ('6', 'c'): 0x3225, ('7', 'c'): 0x3226, ('8', 'c'): 0x3227, ('9', 'c'): 0x3228, # code points 0xe000 - 0xefff excluded, they have no assigned # characters, only used in proposals. ('f', 'f'): 0xfb00, ('f', 'i'): 0xfb01, ('f', 'l'): 0xfb02, ('f', 't'): 0xfb05, ('s', 't'): 0xfb06, # Vim 5.x compatible digraphs that don't conflict with the above ('~', '!'): 161, ('c', '|'): 162, ('$', '$'): 163, ('o', 'x'): 164, # currency symbol in ISO 8859-1 ('Y', '-'): 165, ('|', '|'): 166, ('c', 'O'): 169, ('-', ','): 172, ('-', '='): 175, ('~', 'o'): 176, ('2', '2'): 178, ('3', '3'): 179, ('p', 'p'): 182, ('~', '.'): 183, ('1', '1'): 185, ('~', '?'): 191, ('A', '`'): 192, ('A', '^'): 194, ('A', '~'): 195, ('A', '"'): 196, ('A', '@'): 197, ('E', '`'): 200, ('E', '^'): 202, ('E', '"'): 203, ('I', '`'): 204, ('I', '^'): 206, ('I', '"'): 207, ('N', '~'): 209, ('O', '`'): 210, ('O', '^'): 212, ('O', '~'): 213, ('/', '\\'): 215, # multiplication symbol in ISO 8859-1 ('U', '`'): 217, ('U', '^'): 219, ('I', 'p'): 222, ('a', '`'): 224, ('a', '^'): 226, ('a', '~'): 227, ('a', '"'): 228, ('a', '@'): 229, ('e', '`'): 232, ('e', '^'): 234, ('e', '"'): 235, ('i', '`'): 236, ('i', '^'): 238, ('n', '~'): 241, ('o', '`'): 242, ('o', '^'): 244, ('o', '~'): 245, ('u', '`'): 249, ('u', '^'): 251, ('y', '"'): 255, } prompt_toolkit-2.0.10/prompt_toolkit/key_binding/emacs_state.py0000644000175100017510000000141313545407204026525 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = [ 'EmacsState', ] class EmacsState(object): """ Mutable class to hold Emacs specific state. """ def __init__(self): # Simple macro recording. (Like Readline does.) # (For Emacs mode.) self.macro = [] self.current_recording = None def reset(self): self.current_recording = None @property def is_recording(self): " Tell whether we are recording a macro. " return self.current_recording is not None def start_macro(self): " Start recording macro. " self.current_recording = [] def end_macro(self): " End recording macro. " self.macro = self.current_recording self.current_recording = None prompt_toolkit-2.0.10/prompt_toolkit/key_binding/key_bindings.py0000644000175100017510000004163013545407204026707 0ustar jonathanjonathan00000000000000""" Key bindings registry. A `KeyBindings` object is a container that holds a list of key bindings. It has a very efficient internal data structure for checking which key bindings apply for a pressed key. Typical usage:: kb = KeyBindings() @kb.add(Keys.ControlX, Keys.ControlC, filter=INSERT) def handler(event): # Handle ControlX-ControlC key sequence. pass It is also possible to combine multiple KeyBindings objects. We do this in the default key bindings. There are some KeyBindings objects that contain the Emacs bindings, while others contain the Vi bindings. They are merged together using `merge_key_bindings`. We also have a `ConditionalKeyBindings` object that can enable/disable a group of key bindings at once. It is also possible to add a filter to a function, before a key binding has been assigned, through the `key_binding` decorator.:: # First define a key handler with the `filter`. @key_binding(filter=condition) def my_key_binding(event): ... # Later, add it to the key bindings. kb.add(Keys.A, my_key_binding) """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod, abstractproperty from six import text_type, with_metaclass from prompt_toolkit.cache import SimpleCache from prompt_toolkit.filters import Never, to_filter from prompt_toolkit.keys import ALL_KEYS, KEY_ALIASES, Keys __all__ = [ 'KeyBindingsBase', 'KeyBindings', 'ConditionalKeyBindings', 'merge_key_bindings', 'DynamicKeyBindings', 'GlobalOnlyKeyBindings', ] class _Binding(object): """ (Immutable binding class.) :param record_in_macro: When True, don't record this key binding when a macro is recorded. """ def __init__(self, keys, handler, filter=True, eager=False, is_global=False, save_before=None, record_in_macro=True): assert isinstance(keys, tuple) assert callable(handler) assert callable(save_before) self.keys = keys self.handler = handler self.filter = to_filter(filter) self.eager = to_filter(eager) self.is_global = to_filter(is_global) self.save_before = save_before self.record_in_macro = to_filter(record_in_macro) def call(self, event): return self.handler(event) def __repr__(self): return '%s(keys=%r, handler=%r)' % ( self.__class__.__name__, self.keys, self.handler) class KeyBindingsBase(with_metaclass(ABCMeta, object)): """ Interface for a KeyBindings. """ @abstractproperty def _version(self): """ For cache invalidation. - This should increase every time that something changes. """ return 0 @abstractmethod def get_bindings_for_keys(self, keys): """ Return a list of key bindings that can handle these keys. (This return also inactive bindings, so the `filter` still has to be called, for checking it.) :param keys: tuple of keys. """ return [] @abstractmethod def get_bindings_starting_with_keys(self, keys): """ Return a list of key bindings that handle a key sequence starting with `keys`. (It does only return bindings for which the sequences are longer than `keys`. And like `get_bindings_for_keys`, it also includes inactive bindings.) :param keys: tuple of keys. """ return [] # `add` and `remove` don't have to be part of this interface. class KeyBindings(KeyBindingsBase): """ A container for a set of key bindings. Example usage:: kb = KeyBindings() @kb.add('c-t') def _(event): print('Control-T pressed') @kb.add('c-a', 'c-b') def _(event): print('Control-A pressed, followed by Control-B') @kb.add('c-x', filter=is_searching) def _(event): print('Control-X pressed') # Works only if we are searching. """ def __init__(self): self.bindings = [] self._get_bindings_for_keys_cache = SimpleCache(maxsize=10000) self._get_bindings_starting_with_keys_cache = SimpleCache(maxsize=1000) self.__version = 0 # For cache invalidation. def _clear_cache(self): self.__version += 1 self._get_bindings_for_keys_cache.clear() self._get_bindings_starting_with_keys_cache.clear() @property def _version(self): return self.__version def add(self, *keys, **kwargs): """ Decorator for adding a key bindings. :param filter: :class:`~prompt_toolkit.filters.Filter` to determine when this key binding is active. :param eager: :class:`~prompt_toolkit.filters.Filter` or `bool`. When True, ignore potential longer matches when this key binding is hit. E.g. when there is an active eager key binding for Ctrl-X, execute the handler immediately and ignore the key binding for Ctrl-X Ctrl-E of which it is a prefix. :param is_global: When this key bindings is added to a `Container` or `Control`, make it a global (always active) binding. :param save_before: Callable that takes an `Event` and returns True if we should save the current buffer, before handling the event. (That's the default.) :param record_in_macro: Record these key bindings when a macro is being recorded. (True by default.) """ filter = to_filter(kwargs.pop('filter', True)) eager = to_filter(kwargs.pop('eager', False)) is_global = to_filter(kwargs.pop('is_global', False)) save_before = kwargs.pop('save_before', lambda e: True) record_in_macro = to_filter(kwargs.pop('record_in_macro', True)) assert not kwargs assert keys assert callable(save_before) keys = tuple(_check_and_expand_key(k) for k in keys) if isinstance(filter, Never): # When a filter is Never, it will always stay disabled, so in that # case don't bother putting it in the key bindings. It will slow # down every key press otherwise. def decorator(func): return func else: def decorator(func): if isinstance(func, _Binding): # We're adding an existing _Binding object. self.bindings.append( _Binding( keys, func.handler, filter=func.filter & filter, eager=eager | func.eager, is_global = is_global | func.is_global, save_before=func.save_before, record_in_macro=func.record_in_macro)) else: self.bindings.append( _Binding(keys, func, filter=filter, eager=eager, is_global=is_global, save_before=save_before, record_in_macro=record_in_macro)) self._clear_cache() return func return decorator def remove(self, *args): """ Remove a key binding. This expects either a function that was given to `add` method as parameter or a sequence of key bindings. Raises `ValueError` when no bindings was found. Usage:: remove(handler) # Pass handler. remove('c-x', 'c-a') # Or pass the key bindings. """ found = False if callable(args[0]): assert len(args) == 1 function = args[0] # Remove the given function. for b in self.bindings: if b.handler == function: self.bindings.remove(b) found = True else: assert len(args) > 0 # Remove this sequence of key bindings. keys = tuple(_check_and_expand_key(k) for k in args) for b in self.bindings: if b.keys == keys: self.bindings.remove(b) found = True if found: self._clear_cache() else: # No key binding found for this function. Raise ValueError. raise ValueError('Binding not found: %r' % (function, )) # For backwards-compatibility. add_binding = add remove_binding = remove def get_bindings_for_keys(self, keys): """ Return a list of key bindings that can handle this key. (This return also inactive bindings, so the `filter` still has to be called, for checking it.) :param keys: tuple of keys. """ def get(): result = [] for b in self.bindings: if len(keys) == len(b.keys): match = True any_count = 0 for i, j in zip(b.keys, keys): if i != j and i != Keys.Any: match = False break if i == Keys.Any: any_count += 1 if match: result.append((any_count, b)) # Place bindings that have more 'Any' occurrences in them at the end. result = sorted(result, key=lambda item: -item[0]) return [item[1] for item in result] return self._get_bindings_for_keys_cache.get(keys, get) def get_bindings_starting_with_keys(self, keys): """ Return a list of key bindings that handle a key sequence starting with `keys`. (It does only return bindings for which the sequences are longer than `keys`. And like `get_bindings_for_keys`, it also includes inactive bindings.) :param keys: tuple of keys. """ def get(): result = [] for b in self.bindings: if len(keys) < len(b.keys): match = True for i, j in zip(b.keys, keys): if i != j and i != Keys.Any: match = False break if match: result.append(b) return result return self._get_bindings_starting_with_keys_cache.get(keys, get) def _check_and_expand_key(key): """ Replace key by alias and verify whether it's a valid one. """ # Lookup aliases. key = KEY_ALIASES.get(key, key) # Replace 'space' by ' ' if key == 'space': key = ' ' # Final validation. assert isinstance(key, text_type), 'Got %r' % (key, ) if len(key) != 1 and key not in ALL_KEYS: raise ValueError('Invalid key: %s' % (key, )) return key def key_binding(filter=True, eager=False, is_global=False, save_before=None, record_in_macro=True): """ Decorator that turn a function into a `_Binding` object. This can be added to a `KeyBindings` object when a key binding is assigned. """ assert save_before is None or callable(save_before) filter = to_filter(filter) eager = to_filter(eager) is_global = to_filter(is_global) save_before = save_before or (lambda event: True) record_in_macro = to_filter(record_in_macro) keys = () def decorator(function): return _Binding(keys, function, filter=filter, eager=eager, is_global=is_global, save_before=save_before, record_in_macro=record_in_macro) return decorator class _Proxy(KeyBindingsBase): """ Common part for ConditionalKeyBindings and _MergedKeyBindings. """ def __init__(self): # `KeyBindings` to be synchronized with all the others. self._bindings2 = KeyBindings() self._last_version = None def _update_cache(self): """ If `self._last_version` is outdated, then this should update the version and `self._bindings2`. """ raise NotImplementedError # Proxy methods to self._bindings2. @property def bindings(self): self._update_cache() return self._bindings2.bindings @property def _version(self): self._update_cache() return self._last_version def get_bindings_for_keys(self, *a, **kw): self._update_cache() return self._bindings2.get_bindings_for_keys(*a, **kw) def get_bindings_starting_with_keys(self, *a, **kw): self._update_cache() return self._bindings2.get_bindings_starting_with_keys(*a, **kw) class ConditionalKeyBindings(_Proxy): """ Wraps around a `KeyBindings`. Disable/enable all the key bindings according to the given (additional) filter.:: @Condition def setting_is_true(): return True # or False registry = ConditionalKeyBindings(key_bindings, setting_is_true) When new key bindings are added to this object. They are also enable/disabled according to the given `filter`. :param registries: List of :class:`.KeyBindings` objects. :param filter: :class:`~prompt_toolkit.filters.Filter` object. """ def __init__(self, key_bindings, filter=True): assert isinstance(key_bindings, KeyBindingsBase) _Proxy.__init__(self) self.key_bindings = key_bindings self.filter = to_filter(filter) def _update_cache(self): " If the original key bindings was changed. Update our copy version. " expected_version = self.key_bindings._version if self._last_version != expected_version: bindings2 = KeyBindings() # Copy all bindings from `self.key_bindings`, adding our condition. for b in self.key_bindings.bindings: bindings2.bindings.append( _Binding( keys=b.keys, handler=b.handler, filter=self.filter & b.filter, eager=b.eager, is_global=b.is_global, save_before=b.save_before, record_in_macro=b.record_in_macro)) self._bindings2 = bindings2 self._last_version = expected_version class _MergedKeyBindings(_Proxy): """ Merge multiple registries of key bindings into one. This class acts as a proxy to multiple :class:`.KeyBindings` objects, but behaves as if this is just one bigger :class:`.KeyBindings`. :param registries: List of :class:`.KeyBindings` objects. """ def __init__(self, registries): assert all(isinstance(r, KeyBindingsBase) for r in registries) _Proxy.__init__(self) self.registries = registries def _update_cache(self): """ If one of the original registries was changed. Update our merged version. """ expected_version = tuple(r._version for r in self.registries) if self._last_version != expected_version: bindings2 = KeyBindings() for reg in self.registries: bindings2.bindings.extend(reg.bindings) self._bindings2 = bindings2 self._last_version = expected_version def merge_key_bindings(bindings): """ Merge multiple :class:`.Keybinding` objects together. Usage:: bindings = merge_key_bindings([bindings1, bindings2, ...]) """ return _MergedKeyBindings(bindings) class DynamicKeyBindings(_Proxy): """ KeyBindings class that can dynamically returns any KeyBindings. :param get_key_bindings: Callable that returns a :class:`.KeyBindings` instance. """ def __init__(self, get_key_bindings): assert callable(get_key_bindings) self.get_key_bindings = get_key_bindings self.__version = 0 self._last_child_version = None self._dummy = KeyBindings() # Empty key bindings. def _update_cache(self): key_bindings = self.get_key_bindings() or self._dummy assert isinstance(key_bindings, KeyBindingsBase) version = id(key_bindings), key_bindings._version self._bindings2 = key_bindings self._last_version = version class GlobalOnlyKeyBindings(_Proxy): """ Wrapper around a :class:`.KeyBindings` object that only exposes the global key bindings. """ def __init__(self, key_bindings): assert isinstance(key_bindings, KeyBindingsBase) _Proxy.__init__(self) self.key_bindings = key_bindings def _update_cache(self): """ If one of the original registries was changed. Update our merged version. """ expected_version = self.key_bindings._version if self._last_version != expected_version: bindings2 = KeyBindings() for b in self.key_bindings.bindings: if b.is_global(): bindings2.bindings.append(b) self._bindings2 = bindings2 self._last_version = expected_version prompt_toolkit-2.0.10/prompt_toolkit/key_binding/key_processor.py0000644000175100017510000004024313545407204027130 0ustar jonathanjonathan00000000000000# *** encoding: utf-8 *** """ An :class:`~.KeyProcessor` receives callbacks for the keystrokes parsed from the input in the :class:`~prompt_toolkit.inputstream.InputStream` instance. The `KeyProcessor` will according to the implemented keybindings call the correct callbacks when new key presses are feed through `feed`. """ from __future__ import unicode_literals import time import weakref from collections import deque import six from six.moves import range from prompt_toolkit.application.current import get_app from prompt_toolkit.buffer import EditReadOnlyBuffer from prompt_toolkit.enums import EditingMode from prompt_toolkit.eventloop import call_from_executor, run_in_executor from prompt_toolkit.filters.app import vi_navigation_mode from prompt_toolkit.keys import ALL_KEYS, Keys from prompt_toolkit.utils import Event from .key_bindings import KeyBindingsBase __all__ = [ 'KeyProcessor', 'KeyPress', 'KeyPressEvent', ] class KeyPress(object): """ :param key: A `Keys` instance or text (one character). :param data: The received string on stdin. (Often vt100 escape codes.) """ def __init__(self, key, data=None): assert key in ALL_KEYS or len(key) == 1 assert data is None or isinstance(data, six.text_type) if data is None: data = key self.key = key self.data = data def __repr__(self): return '%s(key=%r, data=%r)' % ( self.__class__.__name__, self.key, self.data) def __eq__(self, other): return self.key == other.key and self.data == other.data """ Helper object to indicate flush operation in the KeyProcessor. NOTE: the implementation is very similar to the VT100 parser. """ _Flush = KeyPress('?', data='_Flush') class KeyProcessor(object): """ Statemachine that receives :class:`KeyPress` instances and according to the key bindings in the given :class:`KeyBindings`, calls the matching handlers. :: p = KeyProcessor(key_bindings) # Send keys into the processor. p.feed(KeyPress(Keys.ControlX, '\x18')) p.feed(KeyPress(Keys.ControlC, '\x03') # Process all the keys in the queue. p.process_keys() # Now the ControlX-ControlC callback will be called if this sequence is # registered in the key bindings. :param key_bindings: `KeyBindingsBase` instance. """ def __init__(self, key_bindings): assert isinstance(key_bindings, KeyBindingsBase) self._bindings = key_bindings self.before_key_press = Event(self) self.after_key_press = Event(self) self._keys_pressed = 0 # Monotonically increasing counter. self.reset() def reset(self): self._previous_key_sequence = [] self._previous_handler = None # The queue of keys not yet send to our _process generator/state machine. self.input_queue = deque() # The key buffer that is matched in the generator state machine. # (This is at at most the amount of keys that make up for one key binding.) self.key_buffer = [] #: Readline argument (for repetition of commands.) #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html self.arg = None # Start the processor coroutine. self._process_coroutine = self._process() self._process_coroutine.send(None) def _get_matches(self, key_presses): """ For a list of :class:`KeyPress` instances. Give the matching handlers that would handle this. """ keys = tuple(k.key for k in key_presses) # Try match, with mode flag return [b for b in self._bindings.get_bindings_for_keys(keys) if b.filter()] def _is_prefix_of_longer_match(self, key_presses): """ For a list of :class:`KeyPress` instances. Return True if there is any handler that is bound to a suffix of this keys. """ keys = tuple(k.key for k in key_presses) # Get the filters for all the key bindings that have a longer match. # Note that we transform it into a `set`, because we don't care about # the actual bindings and executing it more than once doesn't make # sense. (Many key bindings share the same filter.) filters = set(b.filter for b in self._bindings.get_bindings_starting_with_keys(keys)) # When any key binding is active, return True. return any(f() for f in filters) def _process(self): """ Coroutine implementing the key match algorithm. Key strokes are sent into this generator, and it calls the appropriate handlers. """ buffer = self.key_buffer retry = False while True: flush = False if retry: retry = False else: key = yield if key is _Flush: flush = True else: buffer.append(key) # If we have some key presses, check for matches. if buffer: matches = self._get_matches(buffer) if flush: is_prefix_of_longer_match = False else: is_prefix_of_longer_match = self._is_prefix_of_longer_match(buffer) # When eager matches were found, give priority to them and also # ignore all the longer matches. eager_matches = [m for m in matches if m.eager()] if eager_matches: matches = eager_matches is_prefix_of_longer_match = False # Exact matches found, call handler. if not is_prefix_of_longer_match and matches: self._call_handler(matches[-1], key_sequence=buffer[:]) del buffer[:] # Keep reference. # No match found. elif not is_prefix_of_longer_match and not matches: retry = True found = False # Loop over the input, try longest match first and shift. for i in range(len(buffer), 0, -1): matches = self._get_matches(buffer[:i]) if matches: self._call_handler(matches[-1], key_sequence=buffer[:i]) del buffer[:i] found = True break if not found: del buffer[:1] def feed(self, key_press, first=False): """ Add a new :class:`KeyPress` to the input queue. (Don't forget to call `process_keys` in order to process the queue.) :param first: If true, insert before everything else. """ assert isinstance(key_press, KeyPress) self._keys_pressed += 1 if first: self.input_queue.appendleft(key_press) else: self.input_queue.append(key_press) def feed_multiple(self, key_presses, first=False): """ :param first: If true, insert before everything else. """ self._keys_pressed += len(key_presses) if first: self.input_queue.extendleft(reversed(key_presses)) else: self.input_queue.extend(key_presses) def process_keys(self): """ Process all the keys in the `input_queue`. (To be called after `feed`.) Note: because of the `feed`/`process_keys` separation, it is possible to call `feed` from inside a key binding. This function keeps looping until the queue is empty. """ app = get_app() def not_empty(): # When the application result is set, stop processing keys. (E.g. # if ENTER was received, followed by a few additional key strokes, # leave the other keys in the queue.) if app.is_done: # But if there are still CPRResponse keys in the queue, these # need to be processed. return any(k for k in self.input_queue if k.key == Keys.CPRResponse) else: return bool(self.input_queue) def get_next(): if app.is_done: # Only process CPR responses. Everything else is typeahead. cpr = [k for k in self.input_queue if k.key == Keys.CPRResponse][0] self.input_queue.remove(cpr) return cpr else: return self.input_queue.popleft() keys_processed = False is_flush = False while not_empty(): keys_processed = True # Process next key. key_press = get_next() is_flush = key_press is _Flush is_cpr = key_press.key == Keys.CPRResponse if not is_flush and not is_cpr: self.before_key_press.fire() try: self._process_coroutine.send(key_press) except Exception: # If for some reason something goes wrong in the parser, (maybe # an exception was raised) restart the processor for next time. self.reset() self.empty_queue() app.invalidate() raise if not is_flush and not is_cpr: self.after_key_press.fire() if keys_processed: # Invalidate user interface. app.invalidate() # Skip timeout if the last key was flush. if not is_flush: self._start_timeout() def empty_queue(self): """ Empty the input queue. Return the unprocessed input. """ key_presses = list(self.input_queue) self.input_queue.clear() # Filter out CPRs. We don't want to return these. key_presses = [k for k in key_presses if k.key != Keys.CPRResponse] return key_presses def _call_handler(self, handler, key_sequence=None): app = get_app() was_recording_emacs = app.emacs_state.is_recording was_recording_vi = bool(app.vi_state.recording_register) was_temporary_navigation_mode = app.vi_state.temporary_navigation_mode arg = self.arg self.arg = None event = KeyPressEvent( weakref.ref(self), arg=arg, key_sequence=key_sequence, previous_key_sequence=self._previous_key_sequence, is_repeat=(handler == self._previous_handler)) # Save the state of the current buffer. if handler.save_before(event): event.app.current_buffer.save_to_undo_stack() # Call handler. try: handler.call(event) self._fix_vi_cursor_position(event) except EditReadOnlyBuffer: # When a key binding does an attempt to change a buffer which is # read-only, we can ignore that. We sound a bell and go on. app.output.bell() if was_temporary_navigation_mode: self._leave_vi_temp_navigation_mode(event) self._previous_key_sequence = key_sequence self._previous_handler = handler # Record the key sequence in our macro. (Only if we're in macro mode # before and after executing the key.) if handler.record_in_macro(): if app.emacs_state.is_recording and was_recording_emacs: app.emacs_state.current_recording.extend(key_sequence) if app.vi_state.recording_register and was_recording_vi: for k in key_sequence: app.vi_state.current_recording += k.data def _fix_vi_cursor_position(self, event): """ After every command, make sure that if we are in Vi navigation mode, we never put the cursor after the last character of a line. (Unless it's an empty line.) """ app = event.app buff = app.current_buffer preferred_column = buff.preferred_column if (vi_navigation_mode() and buff.document.is_cursor_at_the_end_of_line and len(buff.document.current_line) > 0): buff.cursor_position -= 1 # Set the preferred_column for arrow up/down again. # (This was cleared after changing the cursor position.) buff.preferred_column = preferred_column def _leave_vi_temp_navigation_mode(self, event): """ If we're in Vi temporary navigation (normal) mode, return to insert/replace mode after executing one action. """ app = event.app if app.editing_mode == EditingMode.VI: # Not waiting for a text object and no argument has been given. if app.vi_state.operator_func is None and self.arg is None: app.vi_state.temporary_navigation_mode = False def _start_timeout(self): """ Start auto flush timeout. Similar to Vim's `timeoutlen` option. Start a background thread with a timer. When this timeout expires and no key was pressed in the meantime, we flush all data in the queue and call the appropriate key binding handlers. """ timeout = get_app().timeoutlen if timeout is None: return counter = self._keys_pressed def wait(): " Wait for timeout. " time.sleep(timeout) if len(self.key_buffer) > 0 and counter == self._keys_pressed: # (No keys pressed in the meantime.) call_from_executor(flush_keys) def flush_keys(): " Flush keys. " self.feed(_Flush) self.process_keys() # Automatically flush keys. # (_daemon needs to be set, otherwise, this will hang the # application for .5 seconds before exiting.) run_in_executor(wait, _daemon=True) class KeyPressEvent(object): """ Key press event, delivered to key bindings. :param key_processor_ref: Weak reference to the `KeyProcessor`. :param arg: Repetition argument. :param key_sequence: List of `KeyPress` instances. :param previouskey_sequence: Previous list of `KeyPress` instances. :param is_repeat: True when the previous event was delivered to the same handler. """ def __init__(self, key_processor_ref, arg=None, key_sequence=None, previous_key_sequence=None, is_repeat=False): self._key_processor_ref = key_processor_ref self.key_sequence = key_sequence self.previous_key_sequence = previous_key_sequence #: True when the previous key sequence was handled by the same handler. self.is_repeat = is_repeat self._arg = arg self._app = get_app() def __repr__(self): return 'KeyPressEvent(arg=%r, key_sequence=%r, is_repeat=%r)' % ( self.arg, self.key_sequence, self.is_repeat) @property def data(self): return self.key_sequence[-1].data @property def key_processor(self): return self._key_processor_ref() @property def app(self): """ The current `Application` object. """ return self._app @property def current_buffer(self): """ The current buffer. """ return self.app.current_buffer @property def arg(self): """ Repetition argument. """ if self._arg == '-': return -1 result = int(self._arg or 1) # Don't exceed a million. if int(result) >= 1000000: result = 1 return result @property def arg_present(self): """ True if repetition argument was explicitly provided. """ return self._arg is not None def append_to_arg_count(self, data): """ Add digit to the input argument. :param data: the typed digit as string """ assert data in '-0123456789' current = self._arg if data == '-': assert current is None or current == '-' result = data elif current is None: result = data else: result = "%s%s" % (current, data) self.key_processor.arg = result @property def cli(self): " For backward-compatibility. " return self.app prompt_toolkit-2.0.10/prompt_toolkit/key_binding/vi_state.py0000644000175100017510000000546313545407204026064 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = [ 'InputMode', 'CharacterFind', 'ViState', ] class InputMode(object): INSERT = 'vi-insert' INSERT_MULTIPLE = 'vi-insert-multiple' NAVIGATION = 'vi-navigation' # Normal mode. REPLACE = 'vi-replace' class CharacterFind(object): def __init__(self, character, backwards=False): self.character = character self.backwards = backwards class ViState(object): """ Mutable class to hold the state of the Vi navigation. """ def __init__(self): #: None or CharacterFind instance. (This is used to repeat the last #: search in Vi mode, by pressing the 'n' or 'N' in navigation mode.) self.last_character_find = None # When an operator is given and we are waiting for text object, # -- e.g. in the case of 'dw', after the 'd' --, an operator callback # is set here. self.operator_func = None self.operator_arg = None #: Named registers. Maps register name (e.g. 'a') to #: :class:`ClipboardData` instances. self.named_registers = {} #: The Vi mode we're currently in to. self.__input_mode = InputMode.INSERT #: Waiting for digraph. self.waiting_for_digraph = False self.digraph_symbol1 = None # (None or a symbol.) #: When true, make ~ act as an operator. self.tilde_operator = False #: Register in which we are recording a macro. #: `None` when not recording anything. # Note that the recording is only stored in the register after the # recording is stopped. So we record in a separate `current_recording` # variable. self.recording_register = None self.current_recording = '' # Temporary navigation (normal) mode. # This happens when control-o has been pressed in insert or replace # mode. The user can now do one navigation action and we'll return back # to insert/replace. self.temporary_navigation_mode = False @property def input_mode(self): " Get `InputMode`. " return self.__input_mode @input_mode.setter def input_mode(self, value): " Set `InputMode`. " if value == InputMode.NAVIGATION: self.waiting_for_digraph = False self.operator_func = None self.operator_arg = None self.__input_mode = value def reset(self): """ Reset state, go back to the given mode. INSERT by default. """ # Go back to insert mode. self.input_mode = InputMode.INSERT self.waiting_for_digraph = False self.operator_func = None self.operator_arg = None # Reset recording state. self.recording_register = None self.current_recording = '' prompt_toolkit-2.0.10/prompt_toolkit/keys.py0000644000175100017510000000536713545407204022742 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = [ 'Keys', 'ALL_KEYS', ] class Keys(object): """ List of keys for use in key bindings. """ Escape = 'escape' # Also Control-[ ControlAt = 'c-@' # Also Control-Space. ControlA = 'c-a' ControlB = 'c-b' ControlC = 'c-c' ControlD = 'c-d' ControlE = 'c-e' ControlF = 'c-f' ControlG = 'c-g' ControlH = 'c-h' ControlI = 'c-i' # Tab ControlJ = 'c-j' # Newline ControlK = 'c-k' ControlL = 'c-l' ControlM = 'c-m' # Carriage return ControlN = 'c-n' ControlO = 'c-o' ControlP = 'c-p' ControlQ = 'c-q' ControlR = 'c-r' ControlS = 'c-s' ControlT = 'c-t' ControlU = 'c-u' ControlV = 'c-v' ControlW = 'c-w' ControlX = 'c-x' ControlY = 'c-y' ControlZ = 'c-z' ControlBackslash = 'c-\\' ControlSquareClose = 'c-]' ControlCircumflex = 'c-^' ControlUnderscore = 'c-_' ControlLeft = 'c-left' ControlRight = 'c-right' ControlUp = 'c-up' ControlDown = 'c-down' Up = 'up' Down = 'down' Right = 'right' Left = 'left' ShiftLeft = 's-left' ShiftUp = 's-up' ShiftDown = 's-down' ShiftRight = 's-right' ShiftDelete = 's-delete' BackTab = 's-tab' # shift + tab Home = 'home' End = 'end' Delete = 'delete' ControlDelete = 'c-delete' PageUp = 'pageup' PageDown = 'pagedown' Insert = 'insert' F1 = 'f1' F2 = 'f2' F3 = 'f3' F4 = 'f4' F5 = 'f5' F6 = 'f6' F7 = 'f7' F8 = 'f8' F9 = 'f9' F10 = 'f10' F11 = 'f11' F12 = 'f12' F13 = 'f13' F14 = 'f14' F15 = 'f15' F16 = 'f16' F17 = 'f17' F18 = 'f18' F19 = 'f19' F20 = 'f20' F21 = 'f21' F22 = 'f22' F23 = 'f23' F24 = 'f24' # Matches any key. Any = '' # Special. ScrollUp = '' ScrollDown = '' CPRResponse = '' Vt100MouseEvent = '' WindowsMouseEvent = '' BracketedPaste = '' # For internal use: key which is ignored. # (The key binding for this key should not do anything.) Ignore = '' ALL_KEYS = [getattr(Keys, k) for k in dir(Keys) if not k.startswith('_')] # Aliases. KEY_ALIASES = { 'backspace': 'c-h', 'c-space': 'c-@', 'enter': 'c-m', 'tab': 'c-i', } # The following should not end up in ALL_KEYS, but we still want them in Keys # for backwards-compatibility. Keys.ControlSpace = Keys.ControlAt Keys.Tab = Keys.ControlI Keys.Enter = Keys.ControlM Keys.Backspace = Keys.ControlH prompt_toolkit-2.0.10/prompt_toolkit/layout/0000755000175100017510000000000013545410361022715 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/layout/__init__.py0000644000175100017510000000661613545407204025041 0ustar jonathanjonathan00000000000000""" Command line layout definitions ------------------------------- The layout of a command line interface is defined by a Container instance. There are two main groups of classes here. Containers and controls: - A container can contain other containers or controls, it can have multiple children and it decides about the dimensions. - A control is responsible for rendering the actual content to a screen. A control can propose some dimensions, but it's the container who decides about the dimensions -- or when the control consumes more space -- which part of the control will be visible. Container classes:: - Container (Abstract base class) |- HSplit (Horizontal split) |- VSplit (Vertical split) |- FloatContainer (Container which can also contain menus and other floats) `- Window (Container which contains one actual control Control classes:: - UIControl (Abstract base class) |- FormattedTextControl (Renders formatted text, or a simple list of text fragments) `- BufferControl (Renders an input buffer.) Usually, you end up wrapping every control inside a `Window` object, because that's the only way to render it in a layout. There are some prepared toolbars which are ready to use:: - SystemToolbar (Shows the 'system' input buffer, for entering system commands.) - ArgToolbar (Shows the input 'arg', for repetition of input commands.) - SearchToolbar (Shows the 'search' input buffer, for incremental search.) - CompletionsToolbar (Shows the completions of the current buffer.) - ValidationToolbar (Shows validation errors of the current buffer.) And one prepared menu: - CompletionsMenu """ from __future__ import unicode_literals from .containers import ( ColorColumn, ConditionalContainer, Container, DynamicContainer, Float, FloatContainer, HorizontalAlign, HSplit, ScrollOffsets, VerticalAlign, VSplit, Window, WindowAlign, WindowRenderInfo, is_container, to_container, to_window, ) from .controls import ( BufferControl, DummyControl, FormattedTextControl, SearchBufferControl, UIContent, UIControl, ) from .dimension import ( D, Dimension, is_dimension, max_layout_dimensions, sum_layout_dimensions, to_dimension, ) from .layout import InvalidLayoutError, Layout, walk from .margins import ( ConditionalMargin, Margin, NumberedMargin, PromptMargin, ScrollbarMargin, ) from .menus import CompletionsMenu, MultiColumnCompletionsMenu __all__ = [ # Layout. 'Layout', 'InvalidLayoutError', 'walk', # Dimensions. 'Dimension', 'D', 'sum_layout_dimensions', 'max_layout_dimensions', 'to_dimension', 'is_dimension', # Containers. 'Container', 'HorizontalAlign', 'VerticalAlign', 'HSplit', 'VSplit', 'FloatContainer', 'Float', 'WindowAlign', 'Window', 'WindowRenderInfo', 'ConditionalContainer', 'ScrollOffsets', 'ColorColumn', 'to_container', 'to_window', 'is_container', 'DynamicContainer', # Controls. 'BufferControl', 'SearchBufferControl', 'DummyControl', 'FormattedTextControl', 'UIControl', 'UIContent', # Margins. 'Margin', 'NumberedMargin', 'ScrollbarMargin', 'ConditionalMargin', 'PromptMargin', # Menus. 'CompletionsMenu', 'MultiColumnCompletionsMenu', ] prompt_toolkit-2.0.10/prompt_toolkit/layout/containers.py0000644000175100017510000026030613545407204025445 0ustar jonathanjonathan00000000000000""" Container for the layout. (Containers can contain other containers or user interface controls.) """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from functools import partial from six import text_type, with_metaclass from six.moves import range from prompt_toolkit.application.current import get_app from prompt_toolkit.cache import SimpleCache from prompt_toolkit.filters import emacs_insert_mode, to_filter, vi_insert_mode from prompt_toolkit.formatted_text import to_formatted_text from prompt_toolkit.formatted_text.utils import ( fragment_list_to_text, fragment_list_width, ) from prompt_toolkit.mouse_events import MouseEvent, MouseEventType from prompt_toolkit.utils import get_cwidth, take_using_weights, to_int, to_str from .controls import DummyControl, FormattedTextControl, UIContent, UIControl from .dimension import ( Dimension, is_dimension, max_layout_dimensions, sum_layout_dimensions, to_dimension, ) from .margins import Margin from .screen import _CHAR_CACHE, Point, WritePosition from .utils import explode_text_fragments try: from collections.abc import Sequence except ImportError: from collections import Sequence __all__ = [ 'Container', 'HorizontalAlign', 'VerticalAlign', 'HSplit', 'VSplit', 'FloatContainer', 'Float', 'WindowAlign', 'Window', 'WindowRenderInfo', 'ConditionalContainer', 'ScrollOffsets', 'ColorColumn', 'to_container', 'to_window', 'is_container', 'DynamicContainer', ] class Container(with_metaclass(ABCMeta, object)): """ Base class for user interface layout. """ @abstractmethod def reset(self): """ Reset the state of this container and all the children. (E.g. reset scroll offsets, etc...) """ @abstractmethod def preferred_width(self, max_available_width): """ Return a :class:`~prompt_toolkit.layout.Dimension` that represents the desired width for this container. """ @abstractmethod def preferred_height(self, width, max_available_height): """ Return a :class:`~prompt_toolkit.layout.Dimension` that represents the desired height for this container. """ @abstractmethod def write_to_screen(self, screen, mouse_handlers, write_position, parent_style, erase_bg, z_index): """ Write the actual content to the screen. :param screen: :class:`~prompt_toolkit.layout.screen.Screen` :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`. :param parent_style: Style string to pass to the :class:`.Window` object. This will be applied to all content of the windows. :class:`.VSplit` and :class:`.HSplit` can use it to pass their style down to the windows that they contain. :param z_index: Used for propagating z_index from parent to child. """ def is_modal(self): """ When this container is modal, key bindings from parent containers are not taken into account if a user control in this container is focused. """ return False def get_key_bindings(self): """ Returns a :class:`.KeyBindings` object. These bindings become active when any user control in this container has the focus, except if any containers between this container and the focused user control is modal. """ return None @abstractmethod def get_children(self): """ Return the list of child :class:`.Container` objects. """ return [] def _window_too_small(): " Create a `Window` that displays the 'Window too small' text. " return Window(FormattedTextControl(text= [('class:window-too-small', ' Window too small... ')])) class VerticalAlign: " Alignment for `HSplit`. " TOP = 'TOP' CENTER = 'CENTER' BOTTOM = 'BOTTOM' JUSTIFY = 'JUSTIFY' class HorizontalAlign: " Alignment for `VSplit`. " LEFT = 'LEFT' CENTER = 'CENTER' RIGHT = 'RIGHT' JUSTIFY = 'JUSTIFY' class _Split(Container): """ The common parts of `VSplit` and `HSplit`. """ def __init__(self, children, window_too_small=None, padding=Dimension.exact(0), padding_char=None, padding_style='', width=None, height=None, z_index=None, modal=False, key_bindings=None, style=''): assert window_too_small is None or isinstance(window_too_small, Container) assert isinstance(children, Sequence) assert isinstance(modal, bool) assert callable(style) or isinstance(style, text_type) assert is_dimension(width) assert is_dimension(height) assert z_index is None or isinstance(z_index, int) # `None` means: inherit from parent. assert is_dimension(padding) assert padding_char is None or isinstance(padding_char, text_type) assert isinstance(padding_style, text_type) self.children = [to_container(c) for c in children] self.window_too_small = window_too_small or _window_too_small() self.padding = padding self.padding_char = padding_char self.padding_style = padding_style self.width = width self.height = height self.z_index = z_index self.modal = modal self.key_bindings = key_bindings self.style = style def is_modal(self): return self.modal def get_key_bindings(self): return self.key_bindings def get_children(self): return self.children class HSplit(_Split): """ Several layouts, one stacked above/under the other. :: +--------------------+ | | +--------------------+ | | +--------------------+ By default, this doesn't display a horizontal line between the children, but if this is something you need, then create a HSplit as follows:: HSplit(children=[ ... ], padding_char='-', padding=1, padding_style='#ffff00') :param children: List of child :class:`.Container` objects. :param window_too_small: A :class:`.Container` object that is displayed if there is not enough space for all the children. By default, this is a "Window too small" message. :param align: `VerticalAlign` value. :param width: When given, use this width instead of looking at the children. :param height: When given, use this height instead of looking at the children. :param z_index: (int or None) When specified, this can be used to bring element in front of floating elements. `None` means: inherit from parent. :param style: A style string. :param modal: ``True`` or ``False``. :param key_bindings: ``None`` or a :class:`.KeyBindings` object. :param padding: (`Dimension` or int), size to be used for the padding. :param padding_char: Character to be used for filling in the padding. :param padding_style: Style to applied to the padding. """ def __init__(self, children, window_too_small=None, align=VerticalAlign.JUSTIFY, padding=0, padding_char=None, padding_style='', width=None, height=None, z_index=None, modal=False, key_bindings=None, style=''): super(HSplit, self).__init__( children=children, window_too_small=window_too_small, padding=padding, padding_char=padding_char, padding_style=padding_style, width=width, height=height, z_index=z_index, modal=modal, key_bindings=key_bindings, style=style) self.align = align self._children_cache = SimpleCache(maxsize=1) self._remaining_space_window = Window() # Dummy window. def preferred_width(self, max_available_width): if self.width is not None: return to_dimension(self.width) if self.children: dimensions = [c.preferred_width(max_available_width) for c in self.children] return max_layout_dimensions(dimensions) else: return Dimension() def preferred_height(self, width, max_available_height): if self.height is not None: return to_dimension(self.height) dimensions = [c.preferred_height(width, max_available_height) for c in self._all_children] return sum_layout_dimensions(dimensions) def reset(self): for c in self.children: c.reset() @property def _all_children(self): """ List of child objects, including padding. """ def get(): result = [] # Padding Top. if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM): result.append(Window(width=Dimension(preferred=0))) # The children with padding. for child in self.children: result.append(child) result.append(Window( height=self.padding, char=self.padding_char, style=self.padding_style)) result.pop() # Padding right. if self.align in (VerticalAlign.CENTER, VerticalAlign.TOP): result.append(Window(width=Dimension(preferred=0))) return result return self._children_cache.get(tuple(self.children), get) def write_to_screen(self, screen, mouse_handlers, write_position, parent_style, erase_bg, z_index): """ Render the prompt to a `Screen` instance. :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which the output has to be written. """ sizes = self._divide_heights(write_position) style = parent_style + ' ' + to_str(self.style) z_index = z_index if self.z_index is None else self.z_index if sizes is None: self.window_too_small.write_to_screen( screen, mouse_handlers, write_position, style, erase_bg, z_index) else: # ypos = write_position.ypos xpos = write_position.xpos width = write_position.width # Draw child panes. for s, c in zip(sizes, self._all_children): c.write_to_screen(screen, mouse_handlers, WritePosition(xpos, ypos, width, s), style, erase_bg, z_index) ypos += s # Fill in the remaining space. This happens when a child control # refuses to take more space and we don't have any padding. Adding a # dummy child control for this (in `self._all_children`) is not # desired, because in some situations, it would take more space, even # when it's not required. This is required to apply the styling. remaining_height = write_position.ypos + write_position.height - ypos if remaining_height > 0: self._remaining_space_window.write_to_screen( screen, mouse_handlers, WritePosition(xpos, ypos, width, remaining_height), style, erase_bg, z_index) def _divide_heights(self, write_position): """ Return the heights for all rows. Or None when there is not enough space. """ if not self.children: return [] width = write_position.width height = write_position.height # Calculate heights. dimensions = [ c.preferred_height(width, height) for c in self._all_children] # Sum dimensions sum_dimensions = sum_layout_dimensions(dimensions) # If there is not enough space for both. # Don't do anything. if sum_dimensions.min > height: return # Find optimal sizes. (Start with minimal size, increase until we cover # the whole height.) sizes = [d.min for d in dimensions] child_generator = take_using_weights( items=list(range(len(dimensions))), weights=[d.weight for d in dimensions]) i = next(child_generator) # Increase until we meet at least the 'preferred' size. preferred_stop = min(height, sum_dimensions.preferred) preferred_dimensions = [d.preferred for d in dimensions] while sum(sizes) < preferred_stop: if sizes[i] < preferred_dimensions[i]: sizes[i] += 1 i = next(child_generator) # Increase until we use all the available space. (or until "max") if not get_app().is_done: max_stop = min(height, sum_dimensions.max) max_dimensions = [d.max for d in dimensions] while sum(sizes) < max_stop: if sizes[i] < max_dimensions[i]: sizes[i] += 1 i = next(child_generator) return sizes class VSplit(_Split): """ Several layouts, one stacked left/right of the other. :: +---------+----------+ | | | | | | +---------+----------+ By default, this doesn't display a vertical line between the children, but if this is something you need, then create a HSplit as follows:: VSplit(children=[ ... ], padding_char='|', padding=1, padding_style='#ffff00') :param children: List of child :class:`.Container` objects. :param window_too_small: A :class:`.Container` object that is displayed if there is not enough space for all the children. By default, this is a "Window too small" message. :param align: `HorizontalAlign` value. :param width: When given, use this width instead of looking at the children. :param height: When given, use this height instead of looking at the children. :param z_index: (int or None) When specified, this can be used to bring element in front of floating elements. `None` means: inherit from parent. :param style: A style string. :param modal: ``True`` or ``False``. :param key_bindings: ``None`` or a :class:`.KeyBindings` object. :param padding: (`Dimension` or int), size to be used for the padding. :param padding_char: Character to be used for filling in the padding. :param padding_style: Style to applied to the padding. """ def __init__(self, children, window_too_small=None, align=HorizontalAlign.JUSTIFY, padding=Dimension.exact(0), padding_char=None, padding_style='', width=None, height=None, z_index=None, modal=False, key_bindings=None, style=''): super(VSplit, self).__init__( children=children, window_too_small=window_too_small, padding=padding, padding_char=padding_char, padding_style=padding_style, width=width, height=height, z_index=z_index, modal=modal, key_bindings=key_bindings, style=style) self.align = align self._children_cache = SimpleCache(maxsize=1) self._remaining_space_window = Window() # Dummy window. def preferred_width(self, max_available_width): if self.width is not None: return to_dimension(self.width) dimensions = [c.preferred_width(max_available_width) for c in self._all_children] return sum_layout_dimensions(dimensions) def preferred_height(self, width, max_available_height): if self.height is not None: return to_dimension(self.height) # At the point where we want to calculate the heights, the widths have # already been decided. So we can trust `width` to be the actual # `width` that's going to be used for the rendering. So, # `divide_widths` is supposed to use all of the available width. # Using only the `preferred` width caused a bug where the reported # height was more than required. (we had a `BufferControl` which did # wrap lines because of the smaller width returned by `_divide_widths`. sizes = self._divide_widths(width) children = self._all_children if sizes is None: return Dimension() else: dimensions = [c.preferred_height(s, max_available_height) for s, c in zip(sizes, children)] return max_layout_dimensions(dimensions) def reset(self): for c in self.children: c.reset() @property def _all_children(self): """ List of child objects, including padding. """ def get(): result = [] # Padding left. if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT): result.append(Window(width=Dimension(preferred=0))) # The children with padding. for child in self.children: result.append(child) result.append(Window( width=self.padding, char=self.padding_char, style=self.padding_style)) result.pop() # Padding right. if self.align in (HorizontalAlign.CENTER, HorizontalAlign.LEFT): result.append(Window(width=Dimension(preferred=0))) return result return self._children_cache.get(tuple(self.children), get) def _divide_widths(self, width): """ Return the widths for all columns. Or None when there is not enough space. """ children = self._all_children if not children: return [] # Calculate widths. dimensions = [c.preferred_width(width) for c in children] preferred_dimensions = [d.preferred for d in dimensions] # Sum dimensions sum_dimensions = sum_layout_dimensions(dimensions) # If there is not enough space for both. # Don't do anything. if sum_dimensions.min > width: return # Find optimal sizes. (Start with minimal size, increase until we cover # the whole width.) sizes = [d.min for d in dimensions] child_generator = take_using_weights( items=list(range(len(dimensions))), weights=[d.weight for d in dimensions]) i = next(child_generator) # Increase until we meet at least the 'preferred' size. preferred_stop = min(width, sum_dimensions.preferred) while sum(sizes) < preferred_stop: if sizes[i] < preferred_dimensions[i]: sizes[i] += 1 i = next(child_generator) # Increase until we use all the available space. max_dimensions = [d.max for d in dimensions] max_stop = min(width, sum_dimensions.max) while sum(sizes) < max_stop: if sizes[i] < max_dimensions[i]: sizes[i] += 1 i = next(child_generator) return sizes def write_to_screen(self, screen, mouse_handlers, write_position, parent_style, erase_bg, z_index): """ Render the prompt to a `Screen` instance. :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class to which the output has to be written. """ if not self.children: return children = self._all_children sizes = self._divide_widths(write_position.width) style = parent_style + ' ' + to_str(self.style) z_index = z_index if self.z_index is None else self.z_index # If there is not enough space. if sizes is None: self.window_too_small.write_to_screen( screen, mouse_handlers, write_position, style, erase_bg, z_index) return # Calculate heights, take the largest possible, but not larger than # write_position.height. heights = [child.preferred_height(width, write_position.height).preferred for width, child in zip(sizes, children)] height = max(write_position.height, min(write_position.height, max(heights))) # ypos = write_position.ypos xpos = write_position.xpos # Draw all child panes. for s, c in zip(sizes, children): c.write_to_screen(screen, mouse_handlers, WritePosition(xpos, ypos, s, height), style, erase_bg, z_index) xpos += s # Fill in the remaining space. This happens when a child control # refuses to take more space and we don't have any padding. Adding a # dummy child control for this (in `self._all_children`) is not # desired, because in some situations, it would take more space, even # when it's not required. This is required to apply the styling. remaining_width = write_position.xpos + write_position.width - xpos if remaining_width > 0: self._remaining_space_window.write_to_screen( screen, mouse_handlers, WritePosition(xpos, ypos, remaining_width, height), style, erase_bg, z_index) class FloatContainer(Container): """ Container which can contain another container for the background, as well as a list of floating containers on top of it. Example Usage:: FloatContainer(content=Window(...), floats=[ Float(xcursor=True, ycursor=True, layout=CompletionMenu(...)) ]) :param z_index: (int or None) When specified, this can be used to bring element in front of floating elements. `None` means: inherit from parent. This is the z_index for the whole `Float` container as a whole. """ def __init__(self, content, floats, modal=False, key_bindings=None, style='', z_index=None): assert all(isinstance(f, Float) for f in floats) assert isinstance(modal, bool) assert callable(style) or isinstance(style, text_type) assert z_index is None or isinstance(z_index, int) self.content = to_container(content) self.floats = floats self.modal = modal self.key_bindings = key_bindings self.style = style self.z_index = z_index def reset(self): self.content.reset() for f in self.floats: f.content.reset() def preferred_width(self, write_position): return self.content.preferred_width(write_position) def preferred_height(self, width, max_available_height): """ Return the preferred height of the float container. (We don't care about the height of the floats, they should always fit into the dimensions provided by the container.) """ return self.content.preferred_height(width, max_available_height) def write_to_screen(self, screen, mouse_handlers, write_position, parent_style, erase_bg, z_index): style = parent_style + ' ' + to_str(self.style) z_index = z_index if self.z_index is None else self.z_index self.content.write_to_screen( screen, mouse_handlers, write_position, style, erase_bg, z_index) for number, fl in enumerate(self.floats): # z_index of a Float is computed by summing the z_index of the # container and the `Float`. new_z_index = (z_index or 0) + fl.z_index style = parent_style + ' ' + to_str(self.style) # If the float that we have here, is positioned relative to the # cursor position, but the Window that specifies the cursor # position is not drawn yet, because it's a Float itself, we have # to postpone this calculation. (This is a work-around, but good # enough for now.) postpone = (fl.xcursor is not None or fl.ycursor is not None) if postpone: new_z_index = number + 10 ** 8 # Draw as late as possible, but keep the order. screen.draw_with_z_index(z_index=new_z_index, draw_func=partial(self._draw_float, fl, screen, mouse_handlers, write_position, style, erase_bg, new_z_index)) else: self._draw_float(fl, screen, mouse_handlers, write_position, style, erase_bg, new_z_index) def _draw_float(self, fl, screen, mouse_handlers, write_position, style, erase_bg, z_index): " Draw a single Float. " # When a menu_position was given, use this instead of the cursor # position. (These cursor positions are absolute, translate again # relative to the write_position.) # Note: This should be inside the for-loop, because one float could # set the cursor position to be used for the next one. cpos = screen.get_menu_position(fl.attach_to_window or get_app().layout.current_window) cursor_position = Point(x=cpos.x - write_position.xpos, y=cpos.y - write_position.ypos) fl_width = fl.get_width() fl_height = fl.get_height() # Left & width given. if fl.left is not None and fl_width is not None: xpos = fl.left width = fl_width # Left & right given -> calculate width. elif fl.left is not None and fl.right is not None: xpos = fl.left width = write_position.width - fl.left - fl.right # Width & right given -> calculate left. elif fl_width is not None and fl.right is not None: xpos = write_position.width - fl.right - fl_width width = fl_width elif fl.xcursor: width = fl_width if width is None: width = fl.content.preferred_width(write_position.width).preferred width = min(write_position.width, width) xpos = cursor_position.x if xpos + width > write_position.width: xpos = max(0, write_position.width - width) # Only width given -> center horizontally. elif fl_width: xpos = int((write_position.width - fl_width) / 2) width = fl_width # Otherwise, take preferred width from float content. else: width = fl.content.preferred_width(write_position.width).preferred if fl.left is not None: xpos = fl.left elif fl.right is not None: xpos = max(0, write_position.width - width - fl.right) else: # Center horizontally. xpos = max(0, int((write_position.width - width) / 2)) # Trim. width = min(width, write_position.width - xpos) # Top & height given. if fl.top is not None and fl_height is not None: ypos = fl.top height = fl_height # Top & bottom given -> calculate height. elif fl.top is not None and fl.bottom is not None: ypos = fl.top height = write_position.height - fl.top - fl.bottom # Height & bottom given -> calculate top. elif fl_height is not None and fl.bottom is not None: ypos = write_position.height - fl_height - fl.bottom height = fl_height # Near cursor. elif fl.ycursor: ypos = cursor_position.y + (0 if fl.allow_cover_cursor else 1) height = fl_height if height is None: height = fl.content.preferred_height( width, write_position.height).preferred # Reduce height if not enough space. (We can use the height # when the content requires it.) if height > write_position.height - ypos: if write_position.height - ypos + 1 >= ypos: # When the space below the cursor is more than # the space above, just reduce the height. height = write_position.height - ypos else: # Otherwise, fit the float above the cursor. height = min(height, cursor_position.y) ypos = cursor_position.y - height # Only height given -> center vertically. elif fl_width: ypos = int((write_position.height - fl_height) / 2) height = fl_height # Otherwise, take preferred height from content. else: height = fl.content.preferred_height( width, write_position.height).preferred if fl.top is not None: ypos = fl.top elif fl.bottom is not None: ypos = max(0, write_position.height - height - fl.bottom) else: # Center vertically. ypos = max(0, int((write_position.height - height) / 2)) # Trim. height = min(height, write_position.height - ypos) # Write float. # (xpos and ypos can be negative: a float can be partially visible.) if height > 0 and width > 0: wp = WritePosition(xpos=xpos + write_position.xpos, ypos=ypos + write_position.ypos, width=width, height=height) if not fl.hide_when_covering_content or self._area_is_empty(screen, wp): fl.content.write_to_screen( screen, mouse_handlers, wp, style, erase_bg=not fl.transparent(), z_index=z_index) def _area_is_empty(self, screen, write_position): """ Return True when the area below the write position is still empty. (For floats that should not hide content underneath.) """ wp = write_position for y in range(wp.ypos, wp.ypos + wp.height): if y in screen.data_buffer: row = screen.data_buffer[y] for x in range(wp.xpos, wp.xpos + wp.width): c = row[x] if c.char != ' ': return False return True def is_modal(self): return self.modal def get_key_bindings(self): return self.key_bindings def get_children(self): children = [self.content] children.extend(f.content for f in self.floats) return children class Float(object): """ Float for use in a :class:`.FloatContainer`. Except for the `content` parameter, all other options are optional. :param content: :class:`.Container` instance. :param width: :class:`.Dimension` or callable which returns a :class:`.Dimension`. :param height: :class:`.Dimension` or callable which returns a :class:`.Dimension`. :param left: Distance to the left edge of the :class:`.FloatContainer`. :param right: Distance to the right edge of the :class:`.FloatContainer`. :param top: Distance to the top of the :class:`.FloatContainer`. :param bottom: Distance to the bottom of the :class:`.FloatContainer`. :param attach_to_window: Attach to the cursor from this window, instead of the current window. :param hide_when_covering_content: Hide the float when it covers content underneath. :param allow_cover_cursor: When `False`, make sure to display the float below the cursor. Not on top of the indicated position. :param z_index: Z-index position. For a Float, this needs to be at least one. It is relative to the z_index of the parent container. :param transparent: :class:`.Filter` indicating whether this float needs to be drawn transparently. """ def __init__(self, content=None, top=None, right=None, bottom=None, left=None, width=None, height=None, xcursor=None, ycursor=None, attach_to_window=None, hide_when_covering_content=False, allow_cover_cursor=False, z_index=1, transparent=False): assert is_dimension(width) assert is_dimension(height) assert isinstance(hide_when_covering_content, bool) assert isinstance(allow_cover_cursor, bool) assert isinstance(z_index, int) and z_index >= 1 self.left = left self.right = right self.top = top self.bottom = bottom self.width = width self.height = height self.xcursor = xcursor self.ycursor = ycursor if attach_to_window: self.attach_to_window = to_window(attach_to_window) else: self.attach_to_window = None self.content = to_container(content) self.hide_when_covering_content = hide_when_covering_content self.allow_cover_cursor = allow_cover_cursor self.z_index = z_index self.transparent = to_filter(transparent) def get_width(self): if callable(self.width): return self.width() return self.width def get_height(self): if callable(self.height): return self.height() return self.height def __repr__(self): return 'Float(content=%r)' % self.content class WindowRenderInfo(object): """ Render information, for the last render time of this control. It stores mapping information between the input buffers (in case of a :class:`~prompt_toolkit.layout.controls.BufferControl`) and the actual render position on the output screen. (Could be used for implementation of the Vi 'H' and 'L' key bindings as well as implementing mouse support.) :param ui_content: The original :class:`.UIContent` instance that contains the whole input, without clipping. (ui_content) :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. :param window_width: The width of the window that displays the content, without the margins. :param window_height: The height of the window that displays the content. :param configured_scroll_offsets: The scroll offsets as configured for the :class:`Window` instance. :param visible_line_to_row_col: Mapping that maps the row numbers on the displayed screen (starting from zero for the first visible line) to (row, col) tuples pointing to the row and column of the :class:`.UIContent`. :param rowcol_to_yx: Mapping that maps (row, column) tuples representing coordinates of the :class:`UIContent` to (y, x) absolute coordinates at the rendered screen. """ def __init__(self, window, ui_content, horizontal_scroll, vertical_scroll, window_width, window_height, configured_scroll_offsets, visible_line_to_row_col, rowcol_to_yx, x_offset, y_offset, wrap_lines): assert isinstance(window, Window) assert isinstance(ui_content, UIContent) assert isinstance(horizontal_scroll, int) assert isinstance(vertical_scroll, int) assert isinstance(window_width, int) assert isinstance(window_height, int) assert isinstance(configured_scroll_offsets, ScrollOffsets) assert isinstance(visible_line_to_row_col, dict) assert isinstance(rowcol_to_yx, dict) assert isinstance(x_offset, int) assert isinstance(y_offset, int) assert isinstance(wrap_lines, bool) self.window = window self.ui_content = ui_content self.vertical_scroll = vertical_scroll self.window_width = window_width # Width without margins. self.window_height = window_height self.configured_scroll_offsets = configured_scroll_offsets self.visible_line_to_row_col = visible_line_to_row_col self.wrap_lines = wrap_lines self._rowcol_to_yx = rowcol_to_yx # row/col from input to absolute y/x # screen coordinates. self._x_offset = x_offset self._y_offset = y_offset @property def visible_line_to_input_line(self): return dict( (visible_line, rowcol[0]) for visible_line, rowcol in self.visible_line_to_row_col.items()) @property def cursor_position(self): """ Return the cursor position coordinates, relative to the left/top corner of the rendered screen. """ cpos = self.ui_content.cursor_position try: y, x = self._rowcol_to_yx[cpos.y, cpos.x] except KeyError: # For `DummyControl` for instance, the content can be empty, and so # will `_rowcol_to_yx` be. Return 0/0 by default. return Point(x=0, y=0) else: return Point(x=x - self._x_offset, y=y - self._y_offset) @property def applied_scroll_offsets(self): """ Return a :class:`.ScrollOffsets` instance that indicates the actual offset. This can be less than or equal to what's configured. E.g, when the cursor is completely at the top, the top offset will be zero rather than what's configured. """ if self.displayed_lines[0] == 0: top = 0 else: # Get row where the cursor is displayed. y = self.input_line_to_visible_line[self.ui_content.cursor_position.y] top = min(y, self.configured_scroll_offsets.top) return ScrollOffsets( top=top, bottom=min(self.ui_content.line_count - self.displayed_lines[-1] - 1, self.configured_scroll_offsets.bottom), # For left/right, it probably doesn't make sense to return something. # (We would have to calculate the widths of all the lines and keep # double width characters in mind.) left=0, right=0) @property def displayed_lines(self): """ List of all the visible rows. (Line numbers of the input buffer.) The last line may not be entirely visible. """ return sorted(row for row, col in self.visible_line_to_row_col.values()) @property def input_line_to_visible_line(self): """ Return the dictionary mapping the line numbers of the input buffer to the lines of the screen. When a line spans several rows at the screen, the first row appears in the dictionary. """ result = {} for k, v in self.visible_line_to_input_line.items(): if v in result: result[v] = min(result[v], k) else: result[v] = k return result def first_visible_line(self, after_scroll_offset=False): """ Return the line number (0 based) of the input document that corresponds with the first visible line. """ if after_scroll_offset: return self.displayed_lines[self.applied_scroll_offsets.top] else: return self.displayed_lines[0] def last_visible_line(self, before_scroll_offset=False): """ Like `first_visible_line`, but for the last visible line. """ if before_scroll_offset: return self.displayed_lines[-1 - self.applied_scroll_offsets.bottom] else: return self.displayed_lines[-1] def center_visible_line(self, before_scroll_offset=False, after_scroll_offset=False): """ Like `first_visible_line`, but for the center visible line. """ return (self.first_visible_line(after_scroll_offset) + (self.last_visible_line(before_scroll_offset) - self.first_visible_line(after_scroll_offset)) // 2) @property def content_height(self): """ The full height of the user control. """ return self.ui_content.line_count @property def full_height_visible(self): """ True when the full height is visible (There is no vertical scroll.) """ return self.vertical_scroll == 0 and self.last_visible_line() == self.content_height @property def top_visible(self): """ True when the top of the buffer is visible. """ return self.vertical_scroll == 0 @property def bottom_visible(self): """ True when the bottom of the buffer is visible. """ return self.last_visible_line() == self.content_height - 1 @property def vertical_scroll_percentage(self): """ Vertical scroll as a percentage. (0 means: the top is visible, 100 means: the bottom is visible.) """ if self.bottom_visible: return 100 else: return (100 * self.vertical_scroll // self.content_height) def get_height_for_line(self, lineno): """ Return the height of the given line. (The height that it would take, if this line became visible.) """ if self.wrap_lines: return self.ui_content.get_height_for_line( lineno, self.window_width, self.window.get_line_prefix) else: return 1 class ScrollOffsets(object): """ Scroll offsets for the :class:`.Window` class. Note that left/right offsets only make sense if line wrapping is disabled. """ def __init__(self, top=0, bottom=0, left=0, right=0): assert isinstance(top, int) or callable(top) assert isinstance(bottom, int) or callable(bottom) assert isinstance(left, int) or callable(left) assert isinstance(right, int) or callable(right) self._top = top self._bottom = bottom self._left = left self._right = right @property def top(self): return to_int(self._top) @property def bottom(self): return to_int(self._bottom) @property def left(self): return to_int(self._left) @property def right(self): return to_int(self._right) def __repr__(self): return 'ScrollOffsets(top=%r, bottom=%r, left=%r, right=%r)' % ( self._top, self._bottom, self._left, self._right) class ColorColumn(object): " Column for a :class:`.Window` to be colored. " def __init__(self, position, style='class:color-column'): assert isinstance(position, int) assert isinstance(style, text_type) self.position = position self.style = style _in_insert_mode = vi_insert_mode | emacs_insert_mode class WindowAlign: """ Alignment of the Window content. Note that this is different from `HorizontalAlign` and `VerticalAlign`, which are used for the alignment of the child containers in respectively `VSplit` and `HSplit`. """ LEFT = 'LEFT' RIGHT = 'RIGHT' CENTER = 'CENTER' _ALL = (LEFT, RIGHT, CENTER) class Window(Container): """ Container that holds a control. :param content: :class:`.UIControl` instance. :param width: :class:`.Dimension` instance or callable. :param height: :class:`.Dimension` instance or callable. :param z_index: When specified, this can be used to bring element in front of floating elements. :param dont_extend_width: When `True`, don't take up more width then the preferred width reported by the control. :param dont_extend_height: When `True`, don't take up more width then the preferred height reported by the control. :param ignore_content_width: A `bool` or :class:`.Filter` instance. Ignore the :class:`.UIContent` width when calculating the dimensions. :param ignore_content_height: A `bool` or :class:`.Filter` instance. Ignore the :class:`.UIContent` height when calculating the dimensions. :param left_margins: A list of :class:`.Margin` instance to be displayed on the left. For instance: :class:`~prompt_toolkit.layout.NumberedMargin` can be one of them in order to show line numbers. :param right_margins: Like `left_margins`, but on the other side. :param scroll_offsets: :class:`.ScrollOffsets` instance, representing the preferred amount of lines/columns to be always visible before/after the cursor. When both top and bottom are a very high number, the cursor will be centered vertically most of the time. :param allow_scroll_beyond_bottom: A `bool` or :class:`.Filter` instance. When True, allow scrolling so far, that the top part of the content is not visible anymore, while there is still empty space available at the bottom of the window. In the Vi editor for instance, this is possible. You will see tildes while the top part of the body is hidden. :param wrap_lines: A `bool` or :class:`.Filter` instance. When True, don't scroll horizontally, but wrap lines instead. :param get_vertical_scroll: Callable that takes this window instance as input and returns a preferred vertical scroll. (When this is `None`, the scroll is only determined by the last and current cursor position.) :param get_horizontal_scroll: Callable that takes this window instance as input and returns a preferred vertical scroll. :param always_hide_cursor: A `bool` or :class:`.Filter` instance. When True, never display the cursor, even when the user control specifies a cursor position. :param cursorline: A `bool` or :class:`.Filter` instance. When True, display a cursorline. :param cursorcolumn: A `bool` or :class:`.Filter` instance. When True, display a cursorcolumn. :param colorcolumns: A list of :class:`.ColorColumn` instances that describe the columns to be highlighted, or a callable that returns such a list. :param align: :class:`.WindowAlign` value or callable that returns an :class:`.WindowAlign` value. alignment of content. :param style: A style string. Style to be applied to all the cells in this window. (This can be a callable that returns a string.) :param char: (string) Character to be used for filling the background. This can also be a callable that returns a character. :param get_line_prefix: None or a callable that returns formatted text to be inserted before a line. It takes a line number (int) and a wrap_count and returns formatted text. This can be used for implementation of line continuations, things like Vim "breakindent" and so on. """ def __init__(self, content=None, width=None, height=None, z_index=None, dont_extend_width=False, dont_extend_height=False, ignore_content_width=False, ignore_content_height=False, left_margins=None, right_margins=None, scroll_offsets=None, allow_scroll_beyond_bottom=False, wrap_lines=False, get_vertical_scroll=None, get_horizontal_scroll=None, always_hide_cursor=False, cursorline=False, cursorcolumn=False, colorcolumns=None, align=WindowAlign.LEFT, style='', char=None, get_line_prefix=None): assert content is None or isinstance(content, UIControl) assert is_dimension(width) assert is_dimension(height) assert scroll_offsets is None or isinstance(scroll_offsets, ScrollOffsets) assert left_margins is None or all(isinstance(m, Margin) for m in left_margins) assert right_margins is None or all(isinstance(m, Margin) for m in right_margins) assert get_vertical_scroll is None or callable(get_vertical_scroll) assert get_horizontal_scroll is None or callable(get_horizontal_scroll) assert colorcolumns is None or callable(colorcolumns) or isinstance(colorcolumns, list) assert callable(align) or align in WindowAlign._ALL assert callable(style) or isinstance(style, text_type) assert char is None or callable(char) or isinstance(char, text_type) assert z_index is None or isinstance(z_index, int) assert get_line_prefix is None or callable(get_line_prefix) self.allow_scroll_beyond_bottom = to_filter(allow_scroll_beyond_bottom) self.always_hide_cursor = to_filter(always_hide_cursor) self.wrap_lines = to_filter(wrap_lines) self.cursorline = to_filter(cursorline) self.cursorcolumn = to_filter(cursorcolumn) self.content = content or DummyControl() self.dont_extend_width = to_filter(dont_extend_width) self.dont_extend_height = to_filter(dont_extend_height) self.ignore_content_width = to_filter(ignore_content_width) self.ignore_content_height = to_filter(ignore_content_height) self.left_margins = left_margins or [] self.right_margins = right_margins or [] self.scroll_offsets = scroll_offsets or ScrollOffsets() self.get_vertical_scroll = get_vertical_scroll self.get_horizontal_scroll = get_horizontal_scroll self.colorcolumns = colorcolumns or [] self.align = align self.style = style self.char = char self.get_line_prefix = get_line_prefix self.width = width self.height = height self.z_index = z_index # Cache for the screens generated by the margin. self._ui_content_cache = SimpleCache(maxsize=8) self._margin_width_cache = SimpleCache(maxsize=1) self.reset() def __repr__(self): return 'Window(content=%r)' % self.content def reset(self): self.content.reset() #: Scrolling position of the main content. self.vertical_scroll = 0 self.horizontal_scroll = 0 # Vertical scroll 2: this is the vertical offset that a line is # scrolled if a single line (the one that contains the cursor) consumes # all of the vertical space. self.vertical_scroll_2 = 0 #: Keep render information (mappings between buffer input and render #: output.) self.render_info = None def _get_margin_width(self, margin): """ Return the width for this margin. (Calculate only once per render time.) """ # Margin.get_width, needs to have a UIContent instance. def get_ui_content(): return self._get_ui_content(width=0, height=0) def get_width(): return margin.get_width(get_ui_content) key = (margin, get_app().render_counter) return self._margin_width_cache.get(key, get_width) def preferred_width(self, max_available_width): """ Calculate the preferred width for this window. """ def preferred_content_width(): """ Content width: is only calculated if no exact width for the window was given. """ if self.ignore_content_width(): return None # Calculate the width of the margin. total_margin_width = sum(self._get_margin_width(m) for m in self.left_margins + self.right_margins) # Window of the content. (Can be `None`.) preferred_width = self.content.preferred_width( max_available_width - total_margin_width) if preferred_width is not None: # Include width of the margins. preferred_width += total_margin_width return preferred_width # Merge. return self._merge_dimensions( dimension=to_dimension(self.width), get_preferred=preferred_content_width, dont_extend=self.dont_extend_width()) def preferred_height(self, width, max_available_height): """ Calculate the preferred height for this window. """ def preferred_content_height(): """ Content height: is only calculated if no exact height for the window was given. """ if self.ignore_content_height(): return None total_margin_width = sum(self._get_margin_width(m) for m in self.left_margins + self.right_margins) wrap_lines = self.wrap_lines() return self.content.preferred_height( width - total_margin_width, max_available_height, wrap_lines, self.get_line_prefix) return self._merge_dimensions( dimension=to_dimension(self.height), get_preferred=preferred_content_height, dont_extend=self.dont_extend_height()) @staticmethod def _merge_dimensions(dimension, get_preferred, dont_extend=False): """ Take the Dimension from this `Window` class and the received preferred size from the `UIControl` and return a `Dimension` to report to the parent container. """ dimension = dimension or Dimension() # When a preferred dimension was explicitly given to the Window, # ignore the UIControl. if dimension.preferred_specified: preferred = dimension.preferred else: # Otherwise, calculate the preferred dimension from the UI control # content. preferred = get_preferred() # When a 'preferred' dimension is given by the UIControl, make sure # that it stays within the bounds of the Window. if preferred is not None: if dimension.max_specified: preferred = min(preferred, dimension.max) if dimension.min_specified: preferred = max(preferred, dimension.min) # When a `dont_extend` flag has been given, use the preferred dimension # also as the max dimension. if dont_extend and preferred is not None: max_ = min(dimension.max, preferred) else: max_ = (dimension.max if dimension.max_specified else None) min_ = (dimension.min if dimension.min_specified else None) return Dimension( min=min_, max=max_, preferred=preferred, weight=dimension.weight) def _get_ui_content(self, width, height): """ Create a `UIContent` instance. """ def get_content(): return self.content.create_content(width=width, height=height) key = (get_app().render_counter, width, height) return self._ui_content_cache.get(key, get_content) def _get_digraph_char(self): " Return `False`, or the Digraph symbol to be used. " app = get_app() if app.quoted_insert: return '^' if app.vi_state.waiting_for_digraph: if app.vi_state.digraph_symbol1: return app.vi_state.digraph_symbol1 return '?' return False def write_to_screen(self, screen, mouse_handlers, write_position, parent_style, erase_bg, z_index): """ Write window to screen. This renders the user control, the margins and copies everything over to the absolute position at the given screen. """ z_index = z_index if self.z_index is None else self.z_index draw_func = partial(self._write_to_screen_at_index, screen, mouse_handlers, write_position, parent_style, erase_bg) if z_index is None or z_index <= 0: # When no z_index is given, draw right away. draw_func() else: # Otherwise, postpone. screen.draw_with_z_index(z_index=z_index, draw_func=draw_func) def _write_to_screen_at_index(self, screen, mouse_handlers, write_position, parent_style, erase_bg): # Don't bother writing invisible windows. # (We save some time, but also avoid applying last-line styling.) if write_position.height <= 0 or write_position.width <= 0: return # Calculate margin sizes. left_margin_widths = [self._get_margin_width(m) for m in self.left_margins] right_margin_widths = [self._get_margin_width(m) for m in self.right_margins] total_margin_width = sum(left_margin_widths + right_margin_widths) # Render UserControl. ui_content = self.content.create_content( write_position.width - total_margin_width, write_position.height) assert isinstance(ui_content, UIContent) # Scroll content. wrap_lines = self.wrap_lines() self._scroll(ui_content, write_position.width - total_margin_width, write_position.height) # Erase background and fill with `char`. self._fill_bg(screen, write_position, erase_bg) # Resolve `align` attribute. align = self.align() if callable(self.align) else self.align # Write body visible_line_to_row_col, rowcol_to_yx = self._copy_body( ui_content, screen, write_position, sum(left_margin_widths), write_position.width - total_margin_width, self.vertical_scroll, self.horizontal_scroll, wrap_lines=wrap_lines, highlight_lines=True, vertical_scroll_2=self.vertical_scroll_2, always_hide_cursor=self.always_hide_cursor(), has_focus=get_app().layout.current_control == self.content, align=align, get_line_prefix=self.get_line_prefix) # Remember render info. (Set before generating the margins. They need this.) x_offset = write_position.xpos + sum(left_margin_widths) y_offset = write_position.ypos self.render_info = WindowRenderInfo( window=self, ui_content=ui_content, horizontal_scroll=self.horizontal_scroll, vertical_scroll=self.vertical_scroll, window_width=write_position.width - total_margin_width, window_height=write_position.height, configured_scroll_offsets=self.scroll_offsets, visible_line_to_row_col=visible_line_to_row_col, rowcol_to_yx=rowcol_to_yx, x_offset=x_offset, y_offset=y_offset, wrap_lines=wrap_lines) # Set mouse handlers. def mouse_handler(mouse_event): """ Wrapper around the mouse_handler of the `UIControl` that turns screen coordinates into line coordinates. """ # Don't handle mouse events outside of the current modal part of # the UI. if self not in get_app().layout.walk_through_modal_area(): return # Find row/col position first. yx_to_rowcol = dict((v, k) for k, v in rowcol_to_yx.items()) y = mouse_event.position.y x = mouse_event.position.x # If clicked below the content area, look for a position in the # last line instead. max_y = write_position.ypos + len(visible_line_to_row_col) - 1 y = min(max_y, y) while x >= 0: try: row, col = yx_to_rowcol[y, x] except KeyError: # Try again. (When clicking on the right side of double # width characters, or on the right side of the input.) x -= 1 else: # Found position, call handler of UIControl. result = self.content.mouse_handler( MouseEvent(position=Point(x=col, y=row), event_type=mouse_event.event_type)) break else: # nobreak. # (No x/y coordinate found for the content. This happens in # case of a DummyControl, that does not have any content. # Report (0,0) instead.) result = self.content.mouse_handler( MouseEvent(position=Point(x=0, y=0), event_type=mouse_event.event_type)) # If it returns NotImplemented, handle it here. if result == NotImplemented: return self._mouse_handler(mouse_event) return result mouse_handlers.set_mouse_handler_for_range( x_min=write_position.xpos + sum(left_margin_widths), x_max=write_position.xpos + write_position.width - total_margin_width, y_min=write_position.ypos, y_max=write_position.ypos + write_position.height, handler=mouse_handler) # Render and copy margins. move_x = 0 def render_margin(m, width): " Render margin. Return `Screen`. " # Retrieve margin fragments. fragments = m.create_margin(self.render_info, width, write_position.height) # Turn it into a UIContent object. # already rendered those fragments using this size.) return FormattedTextControl(fragments).create_content( width + 1, write_position.height) for m, width in zip(self.left_margins, left_margin_widths): if width > 0: # (ConditionalMargin returns a zero width. -- Don't render.) # Create screen for margin. margin_screen = render_margin(m, width) # Copy and shift X. self._copy_margin(margin_screen, screen, write_position, move_x, width) move_x += width move_x = write_position.width - sum(right_margin_widths) for m, width in zip(self.right_margins, right_margin_widths): # Create screen for margin. margin_screen = render_margin(m, width) # Copy and shift X. self._copy_margin(margin_screen, screen, write_position, move_x, width) move_x += width # Apply 'self.style' self._apply_style(screen, write_position, parent_style) # Tell the screen that this user control has been painted. screen.visible_windows.append(self) def _copy_body(self, ui_content, new_screen, write_position, move_x, width, vertical_scroll=0, horizontal_scroll=0, wrap_lines=False, highlight_lines=False, vertical_scroll_2=0, always_hide_cursor=False, has_focus=False, align=WindowAlign.LEFT, get_line_prefix=None): """ Copy the UIContent into the output screen. :param get_line_prefix: None or a callable that takes a line number (int) and a wrap_count (int) and returns formatted text. """ xpos = write_position.xpos + move_x ypos = write_position.ypos line_count = ui_content.line_count new_buffer = new_screen.data_buffer empty_char = _CHAR_CACHE['', ''] # Map visible line number to (row, col) of input. # 'col' will always be zero if line wrapping is off. visible_line_to_row_col = {} rowcol_to_yx = {} # Maps (row, col) from the input to (y, x) screen coordinates. def copy_line(line, lineno, x, y, is_input=False): """ Copy over a single line to the output screen. This can wrap over multiple lines in the output. It will call the prefix (prompt) function before every line. """ if is_input: current_rowcol_to_yx = rowcol_to_yx else: current_rowcol_to_yx = {} # Throwaway dictionary. # Draw line prefix. if is_input and get_line_prefix: prompt = to_formatted_text(get_line_prefix(lineno, 0)) x, y = copy_line(prompt, lineno, x, y, is_input=False) # Scroll horizontally. skipped = 0 # Characters skipped because of horizontal scrolling. if horizontal_scroll and is_input: h_scroll = horizontal_scroll line = explode_text_fragments(line) while h_scroll > 0 and line: h_scroll -= get_cwidth(line[0][1]) skipped += 1 del line[:1] # Remove first character. x -= h_scroll # When scrolling over double width character, # this can end up being negative. # Align this line. (Note that this doesn't work well when we use # get_line_prefix and that function returns variable width prefixes.) if align == WindowAlign.CENTER: line_width = fragment_list_width(line) if line_width < width: x += (width - line_width) // 2 elif align == WindowAlign.RIGHT: line_width = fragment_list_width(line) if line_width < width: x += width - line_width col = 0 wrap_count = 0 for style, text in line: new_buffer_row = new_buffer[y + ypos] # Remember raw VT escape sequences. (E.g. FinalTerm's # escape sequences.) if '[ZeroWidthEscape]' in style: new_screen.zero_width_escapes[y + ypos][x + xpos] += text continue for c in text: char = _CHAR_CACHE[c, style] char_width = char.width # Wrap when the line width is exceeded. if wrap_lines and x + char_width > width: visible_line_to_row_col[y + 1] = ( lineno, visible_line_to_row_col[y][1] + x) y += 1 wrap_count += 1 x = 0 # Insert line prefix (continuation prompt). if is_input and get_line_prefix: prompt = to_formatted_text( get_line_prefix(lineno, wrap_count)) x, y = copy_line(prompt, lineno, x, y, is_input=False) new_buffer_row = new_buffer[y + ypos] if y >= write_position.height: return x, y # Break out of all for loops. # Set character in screen and shift 'x'. if x >= 0 and y >= 0 and x < write_position.width: new_buffer_row[x + xpos] = char # When we print a multi width character, make sure # to erase the neighbours positions in the screen. # (The empty string if different from everything, # so next redraw this cell will repaint anyway.) if char_width > 1: for i in range(1, char_width): new_buffer_row[x + xpos + i] = empty_char # If this is a zero width characters, then it's # probably part of a decomposed unicode character. # See: https://en.wikipedia.org/wiki/Unicode_equivalence # Merge it in the previous cell. elif char_width == 0: # Handle all character widths. If the previous # character is a multiwidth character, then # merge it two positions back. for pw in [2, 1]: # Previous character width. if x - pw >= 0 and new_buffer_row[x + xpos - pw].width == pw: prev_char = new_buffer_row[x + xpos - pw] char2 = _CHAR_CACHE[prev_char.char + c, prev_char.style] new_buffer_row[x + xpos - pw] = char2 # Keep track of write position for each character. current_rowcol_to_yx[lineno, col + skipped] = (y + ypos, x + xpos) col += 1 x += char_width return x, y # Copy content. def copy(): y = - vertical_scroll_2 lineno = vertical_scroll while y < write_position.height and lineno < line_count: # Take the next line and copy it in the real screen. line = ui_content.get_line(lineno) visible_line_to_row_col[y] = (lineno, horizontal_scroll) # Copy margin and actual line. x = 0 x, y = copy_line(line, lineno, x, y, is_input=True) lineno += 1 y += 1 return y copy() def cursor_pos_to_screen_pos(row, col): " Translate row/col from UIContent to real Screen coordinates. " try: y, x = rowcol_to_yx[row, col] except KeyError: # Normally this should never happen. (It is a bug, if it happens.) # But to be sure, return (0, 0) return Point(x=0, y=0) # raise ValueError( # 'Invalid position. row=%r col=%r, vertical_scroll=%r, ' # 'horizontal_scroll=%r, height=%r' % # (row, col, vertical_scroll, horizontal_scroll, write_position.height)) else: return Point(x=x, y=y) # Set cursor and menu positions. if ui_content.cursor_position: screen_cursor_position = cursor_pos_to_screen_pos( ui_content.cursor_position.y, ui_content.cursor_position.x) if has_focus: new_screen.set_cursor_position(self, screen_cursor_position) if always_hide_cursor: new_screen.show_cursor = False else: new_screen.show_cursor = ui_content.show_cursor self._highlight_digraph(new_screen) if highlight_lines: self._highlight_cursorlines( new_screen, screen_cursor_position, xpos, ypos, width, write_position.height) # Draw input characters from the input processor queue. if has_focus and ui_content.cursor_position: self._show_key_processor_key_buffer(new_screen) # Set menu position. if ui_content.menu_position: new_screen.set_menu_position(self, cursor_pos_to_screen_pos( ui_content.menu_position.y, ui_content.menu_position.x)) # Update output screen height. new_screen.height = max(new_screen.height, ypos + write_position.height) return visible_line_to_row_col, rowcol_to_yx def _fill_bg(self, screen, write_position, erase_bg): """ Erase/fill the background. (Useful for floats and when a `char` has been given.) """ if callable(self.char): char = self.char() else: char = self.char if erase_bg or char: wp = write_position char_obj = _CHAR_CACHE[char or ' ', ''] for y in range(wp.ypos, wp.ypos + wp.height): row = screen.data_buffer[y] for x in range(wp.xpos, wp.xpos + wp.width): row[x] = char_obj def _apply_style(self, new_screen, write_position, parent_style): # Apply `self.style`. style = parent_style + ' ' + to_str(self.style) new_screen.fill_area(write_position, style=style, after=False) # Apply the 'last-line' class to the last line of each Window. This can # be used to apply an 'underline' to the user control. wp = WritePosition(write_position.xpos, write_position.ypos + write_position.height - 1, write_position.width, 1) new_screen.fill_area(wp, 'class:last-line', after=True) def _highlight_digraph(self, new_screen): """ When we are in Vi digraph mode, put a question mark underneath the cursor. """ digraph_char = self._get_digraph_char() if digraph_char: cpos = new_screen.get_cursor_position(self) new_screen.data_buffer[cpos.y][cpos.x] = \ _CHAR_CACHE[digraph_char, 'class:digraph'] def _show_key_processor_key_buffer(self, new_screen): """ When the user is typing a key binding that consists of several keys, display the last pressed key if the user is in insert mode and the key is meaningful to be displayed. E.g. Some people want to bind 'jj' to escape in Vi insert mode. But the first 'j' needs to be displayed in order to get some feedback. """ app = get_app() key_buffer = app.key_processor.key_buffer if key_buffer and _in_insert_mode() and not app.is_done: # The textual data for the given key. (Can be a VT100 escape # sequence.) data = key_buffer[-1].data # Display only if this is a 1 cell width character. if get_cwidth(data) == 1: cpos = new_screen.get_cursor_position(self) new_screen.data_buffer[cpos.y][cpos.x] = \ _CHAR_CACHE[data, 'class:partial-key-binding'] def _highlight_cursorlines(self, new_screen, cpos, x, y, width, height): """ Highlight cursor row/column. """ cursor_line_style = ' class:cursor-line ' cursor_column_style = ' class:cursor-column ' data_buffer = new_screen.data_buffer # Highlight cursor line. if self.cursorline(): row = data_buffer[cpos.y] for x in range(x, x + width): original_char = row[x] row[x] = _CHAR_CACHE[ original_char.char, original_char.style + cursor_line_style] # Highlight cursor column. if self.cursorcolumn(): for y2 in range(y, y + height): row = data_buffer[y2] original_char = row[cpos.x] row[cpos.x] = _CHAR_CACHE[ original_char.char, original_char.style + cursor_column_style] # Highlight color columns colorcolumns = self.colorcolumns if callable(colorcolumns): colorcolumns = colorcolumns() for cc in colorcolumns: assert isinstance(cc, ColorColumn) column = cc.position if column < x + width: # Only draw when visible. color_column_style = ' ' + cc.style for y2 in range(y, y + height): row = data_buffer[y2] original_char = row[column + x] row[column + x] = _CHAR_CACHE[ original_char.char, original_char.style + color_column_style] def _copy_margin(self, lazy_screen, new_screen, write_position, move_x, width): """ Copy characters from the margin screen to the real screen. """ xpos = write_position.xpos + move_x ypos = write_position.ypos margin_write_position = WritePosition(xpos, ypos, width, write_position.height) self._copy_body(lazy_screen, new_screen, margin_write_position, 0, width) def _scroll(self, ui_content, width, height): """ Scroll body. Ensure that the cursor is visible. """ if self.wrap_lines(): func = self._scroll_when_linewrapping else: func = self._scroll_without_linewrapping func(ui_content, width, height) def _scroll_when_linewrapping(self, ui_content, width, height): """ Scroll to make sure the cursor position is visible and that we maintain the requested scroll offset. Set `self.horizontal_scroll/vertical_scroll`. """ scroll_offsets_bottom = self.scroll_offsets.bottom scroll_offsets_top = self.scroll_offsets.top # We don't have horizontal scrolling. self.horizontal_scroll = 0 def get_line_height(lineno): return ui_content.get_height_for_line(lineno, width, self.get_line_prefix) # When there is no space, reset `vertical_scroll_2` to zero and abort. # This can happen if the margin is bigger than the window width. # Otherwise the text height will become "infinite" (a big number) and # the copy_line will spend a huge amount of iterations trying to render # nothing. if width <= 0: self.vertical_scroll = ui_content.cursor_position.y self.vertical_scroll_2 = 0 return # If the current line consumes more than the whole window height, # then we have to scroll vertically inside this line. (We don't take # the scroll offsets into account for this.) # Also, ignore the scroll offsets in this case. Just set the vertical # scroll to this line. line_height = get_line_height(ui_content.cursor_position.y) if line_height > height - scroll_offsets_top: # Calculate the height of the text before the cursor (including # line prefixes). text_before_height = ui_content.get_height_for_line( ui_content.cursor_position.y, width, self.get_line_prefix, slice_stop=ui_content.cursor_position.x) # Adjust scroll offset. self.vertical_scroll = ui_content.cursor_position.y self.vertical_scroll_2 = min( text_before_height - 1, # Keep the cursor visible. line_height - height, # Avoid blank lines at the bottom when scolling up again. self.vertical_scroll_2) self.vertical_scroll_2 = max(0, text_before_height - height, self.vertical_scroll_2) return else: self.vertical_scroll_2 = 0 # Current line doesn't consume the whole height. Take scroll offsets into account. def get_min_vertical_scroll(): # Make sure that the cursor line is not below the bottom. # (Calculate how many lines can be shown between the cursor and the .) used_height = 0 prev_lineno = ui_content.cursor_position.y for lineno in range(ui_content.cursor_position.y, -1, -1): used_height += get_line_height(lineno) if used_height > height - scroll_offsets_bottom: return prev_lineno else: prev_lineno = lineno return 0 def get_max_vertical_scroll(): # Make sure that the cursor line is not above the top. prev_lineno = ui_content.cursor_position.y used_height = 0 for lineno in range(ui_content.cursor_position.y - 1, -1, -1): used_height += get_line_height(lineno) if used_height > scroll_offsets_top: return prev_lineno else: prev_lineno = lineno return prev_lineno def get_topmost_visible(): """ Calculate the upper most line that can be visible, while the bottom is still visible. We should not allow scroll more than this if `allow_scroll_beyond_bottom` is false. """ prev_lineno = ui_content.line_count - 1 used_height = 0 for lineno in range(ui_content.line_count - 1, -1, -1): used_height += get_line_height(lineno) if used_height > height: return prev_lineno else: prev_lineno = lineno return prev_lineno # Scroll vertically. (Make sure that the whole line which contains the # cursor is visible. topmost_visible = get_topmost_visible() # Note: the `min(topmost_visible, ...)` is to make sure that we # don't require scrolling up because of the bottom scroll offset, # when we are at the end of the document. self.vertical_scroll = max(self.vertical_scroll, min(topmost_visible, get_min_vertical_scroll())) self.vertical_scroll = min(self.vertical_scroll, get_max_vertical_scroll()) # Disallow scrolling beyond bottom? if not self.allow_scroll_beyond_bottom(): self.vertical_scroll = min(self.vertical_scroll, topmost_visible) def _scroll_without_linewrapping(self, ui_content, width, height): """ Scroll to make sure the cursor position is visible and that we maintain the requested scroll offset. Set `self.horizontal_scroll/vertical_scroll`. """ cursor_position = ui_content.cursor_position or Point(x=0, y=0) # Without line wrapping, we will never have to scroll vertically inside # a single line. self.vertical_scroll_2 = 0 if ui_content.line_count == 0: self.vertical_scroll = 0 self.horizontal_scroll = 0 return else: current_line_text = fragment_list_to_text(ui_content.get_line(cursor_position.y)) def do_scroll(current_scroll, scroll_offset_start, scroll_offset_end, cursor_pos, window_size, content_size): " Scrolling algorithm. Used for both horizontal and vertical scrolling. " # Calculate the scroll offset to apply. # This can obviously never be more than have the screen size. Also, when the # cursor appears at the top or bottom, we don't apply the offset. scroll_offset_start = int(min(scroll_offset_start, window_size / 2, cursor_pos)) scroll_offset_end = int(min(scroll_offset_end, window_size / 2, content_size - 1 - cursor_pos)) # Prevent negative scroll offsets. if current_scroll < 0: current_scroll = 0 # Scroll back if we scrolled to much and there's still space to show more of the document. if (not self.allow_scroll_beyond_bottom() and current_scroll > content_size - window_size): current_scroll = max(0, content_size - window_size) # Scroll up if cursor is before visible part. if current_scroll > cursor_pos - scroll_offset_start: current_scroll = max(0, cursor_pos - scroll_offset_start) # Scroll down if cursor is after visible part. if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end: current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end return current_scroll # When a preferred scroll is given, take that first into account. if self.get_vertical_scroll: self.vertical_scroll = self.get_vertical_scroll(self) assert isinstance(self.vertical_scroll, int) if self.get_horizontal_scroll: self.horizontal_scroll = self.get_horizontal_scroll(self) assert isinstance(self.horizontal_scroll, int) # Update horizontal/vertical scroll to make sure that the cursor # remains visible. offsets = self.scroll_offsets self.vertical_scroll = do_scroll( current_scroll=self.vertical_scroll, scroll_offset_start=offsets.top, scroll_offset_end=offsets.bottom, cursor_pos=ui_content.cursor_position.y, window_size=height, content_size=ui_content.line_count) if self.get_line_prefix: current_line_prefix_width = fragment_list_width(to_formatted_text( self.get_line_prefix(ui_content.cursor_position.y, 0))) else: current_line_prefix_width = 0 self.horizontal_scroll = do_scroll( current_scroll=self.horizontal_scroll, scroll_offset_start=offsets.left, scroll_offset_end=offsets.right, cursor_pos=get_cwidth(current_line_text[:ui_content.cursor_position.x]), window_size=width - current_line_prefix_width, # We can only analyse the current line. Calculating the width off # all the lines is too expensive. content_size=max(get_cwidth(current_line_text), self.horizontal_scroll + width)) def _mouse_handler(self, mouse_event): """ Mouse handler. Called when the UI control doesn't handle this particular event. """ if mouse_event.event_type == MouseEventType.SCROLL_DOWN: self._scroll_down() elif mouse_event.event_type == MouseEventType.SCROLL_UP: self._scroll_up() def _scroll_down(self): " Scroll window down. " info = self.render_info if self.vertical_scroll < info.content_height - info.window_height: if info.cursor_position.y <= info.configured_scroll_offsets.top: self.content.move_cursor_down() self.vertical_scroll += 1 def _scroll_up(self): " Scroll window up. " info = self.render_info if info.vertical_scroll > 0: # TODO: not entirely correct yet in case of line wrapping and long lines. if info.cursor_position.y >= info.window_height - 1 - info.configured_scroll_offsets.bottom: self.content.move_cursor_up() self.vertical_scroll -= 1 def get_key_bindings(self): return self.content.get_key_bindings() def get_children(self): return [] class ConditionalContainer(Container): """ Wrapper around any other container that can change the visibility. The received `filter` determines whether the given container should be displayed or not. :param content: :class:`.Container` instance. :param filter: :class:`.Filter` instance. """ def __init__(self, content, filter): self.content = to_container(content) self.filter = to_filter(filter) def __repr__(self): return 'ConditionalContainer(%r, filter=%r)' % (self.content, self.filter) def reset(self): self.content.reset() def preferred_width(self, max_available_width): if self.filter(): return self.content.preferred_width(max_available_width) else: return Dimension.zero() def preferred_height(self, width, max_available_height): if self.filter(): return self.content.preferred_height(width, max_available_height) else: return Dimension.zero() def write_to_screen(self, screen, mouse_handlers, write_position, parent_style, erase_bg, z_index): if self.filter(): return self.content.write_to_screen( screen, mouse_handlers, write_position, parent_style, erase_bg, z_index) def get_children(self): return [self.content] class DynamicContainer(Container): """ Container class that can dynamically returns any Container. :param get_container: Callable that returns a :class:`.Container` instance or any widget with a ``__pt_container__`` method. """ def __init__(self, get_container): assert callable(get_container) self.get_container = get_container def _get_container(self): """ Return the current container object. We call `to_container`, because `get_container` can also return a widget with a ``__pt_container__`` method. """ obj = self.get_container() return to_container(obj) def reset(self): self._get_container().reset() def preferred_width(self, max_available_width): return self._get_container().preferred_width(max_available_width) def preferred_height(self, width, max_available_height): return self._get_container().preferred_height(width, max_available_height) def write_to_screen(self, *a, **kw): self._get_container().write_to_screen(*a, **kw) def is_modal(self): return False def get_key_bindings(self): # Key bindings will be collected when `layout.walk()` finds the child # container. return None def get_children(self): # Here we have to return the current active container itself, not its # children. Otherwise, we run into issues where `layout.walk()` will # never see an object of type `Window` if this contains a window. We # can't/shouldn't proxy the "isinstance" check. return [self._get_container()] def to_container(container): """ Make sure that the given object is a :class:`.Container`. """ if isinstance(container, Container): return container elif hasattr(container, '__pt_container__'): return to_container(container.__pt_container__()) else: raise ValueError('Not a container object.') def to_window(container): """ Make sure that the given argument is a :class:`.Window`. """ if isinstance(container, Window): return container elif hasattr(container, '__pt_container__'): return to_window(container.__pt_container__()) else: raise ValueError('Not a Window object: %r.' % (container, )) def is_container(value): """ Checks whether the given value is a container object (for use in assert statements). """ if isinstance(value, Container): return True if hasattr(value, '__pt_container__'): return is_container(value.__pt_container__()) return False prompt_toolkit-2.0.10/prompt_toolkit/layout/controls.py0000644000175100017510000007716313545407204025152 0ustar jonathanjonathan00000000000000""" User interface Controls for the layout. """ from __future__ import unicode_literals import time from abc import ABCMeta, abstractmethod from collections import namedtuple import six from six import with_metaclass from six.moves import range from prompt_toolkit.application.current import get_app from prompt_toolkit.buffer import Buffer from prompt_toolkit.cache import SimpleCache from prompt_toolkit.filters import to_filter from prompt_toolkit.formatted_text import to_formatted_text from prompt_toolkit.formatted_text.utils import ( fragment_list_to_text, fragment_list_width, split_lines, ) from prompt_toolkit.lexers import Lexer, SimpleLexer from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.search import SearchState from prompt_toolkit.selection import SelectionType from prompt_toolkit.utils import get_cwidth from .processors import ( DisplayMultipleCursors, HighlightIncrementalSearchProcessor, HighlightSearchProcessor, HighlightSelectionProcessor, TransformationInput, merge_processors, ) from .screen import Point __all__ = [ 'BufferControl', 'SearchBufferControl', 'DummyControl', 'FormattedTextControl', 'UIControl', 'UIContent', ] class UIControl(with_metaclass(ABCMeta, object)): """ Base class for all user interface controls. """ def reset(self): # Default reset. (Doesn't have to be implemented.) pass def preferred_width(self, max_available_width): return None def preferred_height(self, width, max_available_height, wrap_lines, get_line_prefix): return None def is_focusable(self): """ Tell whether this user control is focusable. """ return False @abstractmethod def create_content(self, width, height): """ Generate the content for this user control. Returns a :class:`.UIContent` instance. """ def mouse_handler(self, mouse_event): """ Handle mouse events. When `NotImplemented` is returned, it means that the given event is not handled by the `UIControl` itself. The `Window` or key bindings can decide to handle this event as scrolling or changing focus. :param mouse_event: `MouseEvent` instance. """ return NotImplemented def move_cursor_down(self): """ Request to move the cursor down. This happens when scrolling down and the cursor is completely at the top. """ def move_cursor_up(self): """ Request to move the cursor up. """ def get_key_bindings(self): """ The key bindings that are specific for this user control. Return a :class:`.KeyBindings` object if some key bindings are specified, or `None` otherwise. """ def get_invalidate_events(self): """ Return a list of `Event` objects. This can be a generator. (The application collects all these events, in order to bind redraw handlers to these events.) """ return [] class UIContent(object): """ Content generated by a user control. This content consists of a list of lines. :param get_line: Callable that takes a line number and returns the current line. This is a list of (style_str, text) tuples. :param line_count: The number of lines. :param cursor_position: a :class:`.Point` for the cursor position. :param menu_position: a :class:`.Point` for the menu position. :param show_cursor: Make the cursor visible. """ def __init__(self, get_line=None, line_count=0, cursor_position=None, menu_position=None, show_cursor=True): assert callable(get_line) assert isinstance(line_count, six.integer_types) assert cursor_position is None or isinstance(cursor_position, Point) assert menu_position is None or isinstance(menu_position, Point) self.get_line = get_line self.line_count = line_count self.cursor_position = cursor_position or Point(x=0, y=0) self.menu_position = menu_position self.show_cursor = show_cursor # Cache for line heights. Maps (lineno, width) -> (height, fragments). self._line_heights_and_fragments = {} def __getitem__(self, lineno): " Make it iterable (iterate line by line). " if lineno < self.line_count: return self.get_line(lineno) else: raise IndexError def get_height_for_line(self, lineno, width, get_line_prefix, slice_stop=None): """ Return the height that a given line would need if it is rendered in a space with the given width (using line wrapping). :param get_line_prefix: None or a `Window.get_line_prefix` callable that returns the prefix to be inserted before this line. :param slice_stop: Wrap only "line[:slice_stop]" and return that partial result. This is needed for scrolling the window correctly when line wrapping. :returns: The computed height. """ # Instead of using `get_line_prefix` as key, we use render_counter # instead. This is more reliable, because this function could still be # the same, while the content would change over time. key = get_app().render_counter, lineno, width, slice_stop try: return self._line_heights_and_fragments[key] except KeyError: if width == 0: height = 10 ** 8 else: # Calculate line width first. line = fragment_list_to_text(self.get_line(lineno))[:slice_stop] text_width = get_cwidth(line) if get_line_prefix: # Add prefix width. text_width += fragment_list_width( to_formatted_text(get_line_prefix(lineno, 0))) # Slower path: compute path when there's a line prefix. height = 1 # Keep wrapping as long as the line doesn't fit. # Keep adding new prefixes for every wrapped line. while text_width > width: height += 1 text_width -= width fragments2 = to_formatted_text( get_line_prefix(lineno, height - 1)) prefix_width = get_cwidth(fragment_list_to_text(fragments2)) if prefix_width >= width: # Prefix doesn't fit. height = 10 ** 8 break text_width += prefix_width else: # Fast path: compute height when there's no line prefix. try: quotient, remainder = divmod(text_width, width) except ZeroDivisionError: height = 10 ** 8 else: if remainder: quotient += 1 # Like math.ceil. height = max(1, quotient) # Cache and return self._line_heights_and_fragments[key] = height return height class FormattedTextControl(UIControl): """ Control that displays formatted text. This can be either plain text, an :class:`~prompt_toolkit.formatted_text.HTML` object an :class:`~prompt_toolkit.formatted_text.ANSI` object or a list of ``(style_str, text)`` tuples, depending on how you prefer to do the formatting. See ``prompt_toolkit.layout.formatted_text`` for more information. (It's mostly optimized for rather small widgets, like toolbars, menus, etc...) When this UI control has the focus, the cursor will be shown in the upper left corner of this control by default. There are two ways for specifying the cursor position: - Pass a `get_cursor_position` function which returns a `Point` instance with the current cursor position. - If the (formatted) text is passed as a list of ``(style, text)`` tuples and there is one that looks like ``('[SetCursorPosition]', '')``, then this will specify the cursor position. Mouse support: The list of fragments can also contain tuples of three items, looking like: (style_str, text, handler). When mouse support is enabled and the user clicks on this fragment, then the given handler is called. That handler should accept two inputs: (Application, MouseEvent) and it should either handle the event or return `NotImplemented` in case we want the containing Window to handle this event. :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable. :param text: Text or formatted text to be displayed. :param style: Style string applied to the content. (If you want to style the whole :class:`~prompt_toolkit.layout.Window`, pass the style to the :class:`~prompt_toolkit.layout.Window` instead.) :param key_bindings: a :class:`.KeyBindings` object. :param get_cursor_position: A callable that returns the cursor position as a `Point` instance. """ def __init__(self, text='', style='', focusable=False, key_bindings=None, show_cursor=True, modal=False, get_cursor_position=None): from prompt_toolkit.key_binding.key_bindings import KeyBindingsBase assert isinstance(style, six.text_type) assert key_bindings is None or isinstance(key_bindings, KeyBindingsBase) assert isinstance(show_cursor, bool) assert isinstance(modal, bool) assert get_cursor_position is None or callable(get_cursor_position) self.text = text # No type check on 'text'. This is done dynamically. self.style = style self.focusable = to_filter(focusable) # Key bindings. self.key_bindings = key_bindings self.show_cursor = show_cursor self.modal = modal self.get_cursor_position = get_cursor_position #: Cache for the content. self._content_cache = SimpleCache(maxsize=18) self._fragment_cache = SimpleCache(maxsize=1) # Only cache one fragment list. We don't need the previous item. # Render info for the mouse support. self._fragments = None def reset(self): self._fragments = None def is_focusable(self): return self.focusable() def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.text) def _get_formatted_text_cached(self): """ Get fragments, but only retrieve fragments once during one render run. (This function is called several times during one rendering, because we also need those for calculating the dimensions.) """ return self._fragment_cache.get( get_app().render_counter, lambda: to_formatted_text(self.text, self.style)) def preferred_width(self, max_available_width): """ Return the preferred width for this control. That is the width of the longest line. """ text = fragment_list_to_text(self._get_formatted_text_cached()) line_lengths = [get_cwidth(l) for l in text.split('\n')] return max(line_lengths) def preferred_height(self, width, max_available_height, wrap_lines, get_line_prefix): content = self.create_content(width, None) return content.line_count def create_content(self, width, height): # Get fragments fragments_with_mouse_handlers = self._get_formatted_text_cached() fragment_lines_with_mouse_handlers = list(split_lines(fragments_with_mouse_handlers)) # Strip mouse handlers from fragments. fragment_lines = [ [tuple(item[:2]) for item in line] for line in fragment_lines_with_mouse_handlers ] # Keep track of the fragments with mouse handler, for later use in # `mouse_handler`. self._fragments = fragments_with_mouse_handlers # If there is a `[SetCursorPosition]` in the fragment list, set the # cursor position here. def get_cursor_position(fragment='[SetCursorPosition]'): for y, line in enumerate(fragment_lines): x = 0 for style_str, text in line: if fragment in style_str: return Point(x=x, y=y) x += len(text) return None # If there is a `[SetMenuPosition]`, set the menu over here. def get_menu_position(): return get_cursor_position('[SetMenuPosition]') cursor_position = (self.get_cursor_position or get_cursor_position)() # Create content, or take it from the cache. key = (tuple(fragments_with_mouse_handlers), width, cursor_position) def get_content(): return UIContent(get_line=lambda i: fragment_lines[i], line_count=len(fragment_lines), show_cursor=self.show_cursor, cursor_position=cursor_position, menu_position=get_menu_position()) return self._content_cache.get(key, get_content) def mouse_handler(self, mouse_event): """ Handle mouse events. (When the fragment list contained mouse handlers and the user clicked on on any of these, the matching handler is called. This handler can still return `NotImplemented` in case we want the :class:`~prompt_toolkit.layout.Window` to handle this particular event.) """ if self._fragments: # Read the generator. fragments_for_line = list(split_lines(self._fragments)) try: fragments = fragments_for_line[mouse_event.position.y] except IndexError: return NotImplemented else: # Find position in the fragment list. xpos = mouse_event.position.x # Find mouse handler for this character. count = 0 for item in fragments: count += len(item[1]) if count >= xpos: if len(item) >= 3: # Handler found. Call it. # (Handler can return NotImplemented, so return # that result.) handler = item[2] return handler(mouse_event) else: break # Otherwise, don't handle here. return NotImplemented def is_modal(self): return self.modal def get_key_bindings(self): return self.key_bindings class DummyControl(UIControl): """ A dummy control object that doesn't paint any content. Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The `fragment` and `char` attributes of the `Window` class can be used to define the filling.) """ def create_content(self, width, height): def get_line(i): return [] return UIContent( get_line=get_line, line_count=100 ** 100) # Something very big. def is_focusable(self): return False _ProcessedLine = namedtuple('_ProcessedLine', 'fragments source_to_display display_to_source') class BufferControl(UIControl): """ Control for visualising the content of a :class:`.Buffer`. :param buffer: The :class:`.Buffer` object to be displayed. :param input_processors: A list of :class:`~prompt_toolkit.layout.processors.Processor` objects. :param include_default_input_processors: When True, include the default processors for highlighting of selection, search and displaying of multiple cursors. :param lexer: :class:`.Lexer` instance for syntax highlighting. :param preview_search: `bool` or :class:`.Filter`: Show search while typing. When this is `True`, probably you want to add a ``HighlightIncrementalSearchProcessor`` as well. Otherwise only the cursor position will move, but the text won't be highlighted. :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable. :param focus_on_click: Focus this buffer when it's click, but not yet focused. :param key_bindings: a :class:`.KeyBindings` object. """ def __init__(self, buffer=None, input_processors=None, include_default_input_processors=True, lexer=None, preview_search=False, focusable=True, search_buffer_control=None, menu_position=None, focus_on_click=False, key_bindings=None): from prompt_toolkit.key_binding.key_bindings import KeyBindingsBase assert buffer is None or isinstance(buffer, Buffer) assert input_processors is None or isinstance(input_processors, list) assert isinstance(include_default_input_processors, bool) assert menu_position is None or callable(menu_position) assert lexer is None or isinstance(lexer, Lexer), 'Got %r' % (lexer, ) assert (search_buffer_control is None or callable(search_buffer_control) or isinstance(search_buffer_control, SearchBufferControl)) assert key_bindings is None or isinstance(key_bindings, KeyBindingsBase) self.input_processors = input_processors self.include_default_input_processors = include_default_input_processors self.default_input_processors = [ HighlightSearchProcessor(), HighlightIncrementalSearchProcessor(), HighlightSelectionProcessor(), DisplayMultipleCursors(), ] self.preview_search = to_filter(preview_search) self.focusable = to_filter(focusable) self.focus_on_click = to_filter(focus_on_click) self.buffer = buffer or Buffer() self.menu_position = menu_position self.lexer = lexer or SimpleLexer() self.key_bindings = key_bindings self._search_buffer_control = search_buffer_control #: Cache for the lexer. #: Often, due to cursor movement, undo/redo and window resizing #: operations, it happens that a short time, the same document has to be #: lexed. This is a fairly easy way to cache such an expensive operation. self._fragment_cache = SimpleCache(maxsize=8) self._xy_to_cursor_position = None self._last_click_timestamp = None self._last_get_processed_line = None def __repr__(self): return '<%s buffer=%r at %r>' % (self.__class__.__name__, self.buffer, id(self)) @property def search_buffer_control(self): if callable(self._search_buffer_control): result = self._search_buffer_control() else: result = self._search_buffer_control assert result is None or isinstance(result, SearchBufferControl) return result @property def search_buffer(self): control = self.search_buffer_control if control is not None: return control.buffer @property def search_state(self): """ Return the `SearchState` for searching this `BufferControl`. This is always associated with the search control. If one search bar is used for searching multiple `BufferControls`, then they share the same `SearchState`. """ search_buffer_control = self.search_buffer_control if search_buffer_control: return search_buffer_control.searcher_search_state else: return SearchState() def is_focusable(self): return self.focusable() def preferred_width(self, max_available_width): """ This should return the preferred width. Note: We don't specify a preferred width according to the content, because it would be too expensive. Calculating the preferred width can be done by calculating the longest line, but this would require applying all the processors to each line. This is unfeasible for a larger document, and doing it for small documents only would result in inconsistent behaviour. """ return None def preferred_height(self, width, max_available_height, wrap_lines, get_line_prefix): # Calculate the content height, if it was drawn on a screen with the # given width. height = 0 content = self.create_content(width, None) # When line wrapping is off, the height should be equal to the amount # of lines. if not wrap_lines: return content.line_count # When the number of lines exceeds the max_available_height, just # return max_available_height. No need to calculate anything. if content.line_count >= max_available_height: return max_available_height for i in range(content.line_count): height += content.get_height_for_line(i, width, get_line_prefix) if height >= max_available_height: return max_available_height return height def _get_formatted_text_for_line_func(self, document): """ Create a function that returns the fragments for a given line. """ # Cache using `document.text`. def get_formatted_text_for_line(): return self.lexer.lex_document(document) key = (document.text, self.lexer.invalidation_hash()) return self._fragment_cache.get(key, get_formatted_text_for_line) def _create_get_processed_line_func(self, document, width, height): """ Create a function that takes a line number of the current document and returns a _ProcessedLine(processed_fragments, source_to_display, display_to_source) tuple. """ # Merge all input processors together. input_processors = self.input_processors or [] if self.include_default_input_processors: input_processors = self.default_input_processors + input_processors merged_processor = merge_processors(input_processors) def transform(lineno, fragments): " Transform the fragments for a given line number. " # Get cursor position at this line. if document.cursor_position_row == lineno: cursor_column = document.cursor_position_col else: cursor_column = None def source_to_display(i): """ X position from the buffer to the x position in the processed fragment list. By default, we start from the 'identity' operation. """ return i transformation = merged_processor.apply_transformation( TransformationInput( self, document, lineno, source_to_display, fragments, width, height)) if cursor_column: cursor_column = transformation.source_to_display(cursor_column) return _ProcessedLine( transformation.fragments, transformation.source_to_display, transformation.display_to_source) def create_func(): get_line = self._get_formatted_text_for_line_func(document) cache = {} def get_processed_line(i): try: return cache[i] except KeyError: processed_line = transform(i, get_line(i)) cache[i] = processed_line return processed_line return get_processed_line return create_func() def create_content(self, width, height, preview_search=False): """ Create a UIContent. """ buffer = self.buffer # Get the document to be shown. If we are currently searching (the # search buffer has focus, and the preview_search filter is enabled), # then use the search document, which has possibly a different # text/cursor position.) search_control = self.search_buffer_control preview_now = preview_search or bool( # Only if this feature is enabled. self.preview_search() and # And something was typed in the associated search field. search_control and search_control.buffer.text and # And we are searching in this control. (Many controls can point to # the same search field, like in Pyvim.) get_app().layout.search_target_buffer_control == self) if preview_now: ss = self.search_state document = buffer.document_for_search(SearchState( text=search_control.buffer.text, direction=ss.direction, ignore_case=ss.ignore_case)) else: document = buffer.document get_processed_line = self._create_get_processed_line_func( document, width, height) self._last_get_processed_line = get_processed_line def translate_rowcol(row, col): " Return the content column for this coordinate. " return Point(x=get_processed_line(row).source_to_display(col), y=row) def get_line(i): " Return the fragments for a given line number. " fragments = get_processed_line(i).fragments # Add a space at the end, because that is a possible cursor # position. (When inserting after the input.) We should do this on # all the lines, not just the line containing the cursor. (Because # otherwise, line wrapping/scrolling could change when moving the # cursor around.) fragments = fragments + [('', ' ')] return fragments content = UIContent( get_line=get_line, line_count=document.line_count, cursor_position=translate_rowcol(document.cursor_position_row, document.cursor_position_col)) # If there is an auto completion going on, use that start point for a # pop-up menu position. (But only when this buffer has the focus -- # there is only one place for a menu, determined by the focused buffer.) if get_app().layout.current_control == self: menu_position = self.menu_position() if self.menu_position else None if menu_position is not None: assert isinstance(menu_position, int) menu_row, menu_col = buffer.document.translate_index_to_position(menu_position) content.menu_position = translate_rowcol(menu_row, menu_col) elif buffer.complete_state: # Position for completion menu. # Note: We use 'min', because the original cursor position could be # behind the input string when the actual completion is for # some reason shorter than the text we had before. (A completion # can change and shorten the input.) menu_row, menu_col = buffer.document.translate_index_to_position( min(buffer.cursor_position, buffer.complete_state.original_document.cursor_position)) content.menu_position = translate_rowcol(menu_row, menu_col) else: content.menu_position = None return content def mouse_handler(self, mouse_event): """ Mouse handler for this control. """ buffer = self.buffer position = mouse_event.position # Focus buffer when clicked. if get_app().layout.current_control == self: if self._last_get_processed_line: processed_line = self._last_get_processed_line(position.y) # Translate coordinates back to the cursor position of the # original input. xpos = processed_line.display_to_source(position.x) index = buffer.document.translate_row_col_to_index(position.y, xpos) # Set the cursor position. if mouse_event.event_type == MouseEventType.MOUSE_DOWN: buffer.exit_selection() buffer.cursor_position = index elif mouse_event.event_type == MouseEventType.MOUSE_UP: # When the cursor was moved to another place, select the text. # (The >1 is actually a small but acceptable workaround for # selecting text in Vi navigation mode. In navigation mode, # the cursor can never be after the text, so the cursor # will be repositioned automatically.) if abs(buffer.cursor_position - index) > 1: buffer.start_selection(selection_type=SelectionType.CHARACTERS) buffer.cursor_position = index # Select word around cursor on double click. # Two MOUSE_UP events in a short timespan are considered a double click. double_click = self._last_click_timestamp and time.time() - self._last_click_timestamp < .3 self._last_click_timestamp = time.time() if double_click: start, end = buffer.document.find_boundaries_of_current_word() buffer.cursor_position += start buffer.start_selection(selection_type=SelectionType.CHARACTERS) buffer.cursor_position += end - start else: # Don't handle scroll events here. return NotImplemented # Not focused, but focusing on click events. else: if self.focus_on_click() and mouse_event.event_type == MouseEventType.MOUSE_UP: # Focus happens on mouseup. (If we did this on mousedown, the # up event will be received at the point where this widget is # focused and be handled anyway.) get_app().layout.current_control = self else: return NotImplemented def move_cursor_down(self): b = self.buffer b.cursor_position += b.document.get_cursor_down_position() def move_cursor_up(self): b = self.buffer b.cursor_position += b.document.get_cursor_up_position() def get_key_bindings(self): """ When additional key bindings are given. Return these. """ return self.key_bindings def get_invalidate_events(self): """ Return the Window invalidate events. """ # Whenever the buffer changes, the UI has to be updated. yield self.buffer.on_text_changed yield self.buffer.on_cursor_position_changed yield self.buffer.on_completions_changed yield self.buffer.on_suggestion_set class SearchBufferControl(BufferControl): """ :class:`.BufferControl` which is used for searching another :class:`.BufferControl`. :param ignore_case: Search case insensitive. """ def __init__(self, buffer=None, input_processors=None, lexer=None, focus_on_click=False, key_bindings=None, ignore_case=False): super(SearchBufferControl, self).__init__( buffer=buffer, input_processors=input_processors, lexer=lexer, focus_on_click=focus_on_click, key_bindings=key_bindings) # If this BufferControl is used as a search field for one or more other # BufferControls, then represents the search state. self.searcher_search_state = SearchState(ignore_case=ignore_case) prompt_toolkit-2.0.10/prompt_toolkit/layout/dimension.py0000644000175100017510000001441013545407204025256 0ustar jonathanjonathan00000000000000""" Layout dimensions are used to give the minimum, maximum and preferred dimensions for containers and controls. """ from __future__ import unicode_literals from prompt_toolkit.utils import test_callable_args __all__ = [ 'Dimension', 'D', 'sum_layout_dimensions', 'max_layout_dimensions', 'to_dimension', 'is_dimension', ] class Dimension(object): """ Specified dimension (width/height) of a user control or window. The layout engine tries to honor the preferred size. If that is not possible, because the terminal is larger or smaller, it tries to keep in between min and max. :param min: Minimum size. :param max: Maximum size. :param weight: For a VSplit/HSplit, the actual size will be determined by taking the proportion of weights from all the children. E.g. When there are two children, one width a weight of 1, and the other with a weight of 2. The second will always be twice as big as the first, if the min/max values allow it. :param preferred: Preferred size. """ def __init__(self, min=None, max=None, weight=None, preferred=None): assert weight is None or (isinstance(weight, int) and weight >= 0) # Cannot be a float. assert min is None or min >= 0 assert max is None or max >= 0 assert preferred is None or preferred >= 0 self.min_specified = min is not None self.max_specified = max is not None self.preferred_specified = preferred is not None self.weight_specified = weight is not None if min is None: min = 0 # Smallest possible value. if max is None: # 0-values are allowed, so use "is None" max = 1000 ** 10 # Something huge. if preferred is None: preferred = min if weight is None: weight = 1 self.min = min self.max = max self.preferred = preferred self.weight = weight # Don't allow situations where max < min. (This would be a bug.) if max < min: raise ValueError('Invalid Dimension: max < min.') # Make sure that the 'preferred' size is always in the min..max range. if self.preferred < self.min: self.preferred = self.min if self.preferred > self.max: self.preferred = self.max @classmethod def exact(cls, amount): """ Return a :class:`.Dimension` with an exact size. (min, max and preferred set to ``amount``). """ return cls(min=amount, max=amount, preferred=amount) @classmethod def zero(cls): """ Create a dimension that represents a zero size. (Used for 'invisible' controls.) """ return cls.exact(amount=0) def is_zero(self): " True if this `Dimension` represents a zero size. " return self.preferred == 0 or self.max == 0 def __repr__(self): fields = [] if self.min_specified: fields.append('min=%r' % self.min) if self.max_specified: fields.append('max=%r' % self.max) if self.preferred_specified: fields.append('preferred=%r' % self.preferred) if self.weight_specified: fields.append('weight=%r' % self.weight) return 'Dimension(%s)' % ', '.join(fields) def sum_layout_dimensions(dimensions): """ Sum a list of :class:`.Dimension` instances. """ min = sum(d.min for d in dimensions) max = sum(d.max for d in dimensions) preferred = sum(d.preferred for d in dimensions) return Dimension(min=min, max=max, preferred=preferred) def max_layout_dimensions(dimensions): """ Take the maximum of a list of :class:`.Dimension` instances. Used when we have a HSplit/VSplit, and we want to get the best width/height.) """ if not len(dimensions): return Dimension.zero() # If all dimensions are size zero. Return zero. # (This is important for HSplit/VSplit, to report the right values to their # parent when all children are invisible.) if all(d.is_zero() for d in dimensions): return dimensions[0] # Ignore empty dimensions. (They should not reduce the size of others.) dimensions = [d for d in dimensions if not d.is_zero()] if dimensions: # Take the highest minimum dimension. min_ = max(d.min for d in dimensions) # For the maximum, we would prefer not to go larger than then smallest # 'max' value, unless other dimensions have a bigger preferred value. # This seems to work best: # - We don't want that a widget with a small height in a VSplit would # shrink other widgets in the split. # If it doesn't work well enough, then it's up to the UI designer to # explicitly pass dimensions. max_ = min(d.max for d in dimensions) max_ = max(max_, max(d.preferred for d in dimensions)) # Make sure that min>=max. In some scenarios, when certain min..max # ranges don't have any overlap, we can end up in such an impossible # situation. In that case, give priority to the max value. # E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8). if min_ > max_: max_ = min_ preferred = max(d.preferred for d in dimensions) return Dimension(min=min_, max=max_, preferred=preferred) else: return Dimension() def to_dimension(value): """ Turn the given object into a `Dimension` object. """ if value is None: return Dimension() if isinstance(value, int): return Dimension.exact(value) if isinstance(value, Dimension): return value if callable(value): return to_dimension(value()) raise ValueError('Not an integer or Dimension object.') def is_dimension(value): """ Test whether the given value could be a valid dimension. (For usage in an assertion. It's not guaranteed in case of a callable.) """ if value is None: return True if callable(value): return test_callable_args(value, []) if isinstance(value, (int, Dimension)): return True return False # Common alias. D = Dimension # For backward-compatibility. LayoutDimension = Dimension prompt_toolkit-2.0.10/prompt_toolkit/layout/dummy.py0000644000175100017510000000165213545407204024430 0ustar jonathanjonathan00000000000000""" Dummy layout. Used when somebody creates an `Application` without specifying a `Layout`. """ from __future__ import unicode_literals from prompt_toolkit.formatted_text import HTML from prompt_toolkit.key_binding import KeyBindings from .containers import Window from .controls import FormattedTextControl from .dimension import D from .layout import Layout __all__ = [ 'create_dummy_layout', ] def create_dummy_layout(): """ Create a dummy layout for use in an 'Application' that doesn't have a layout specified. When ENTER is pressed, the application quits. """ kb = KeyBindings() @kb.add('enter') def enter(event): event.app.exit() control = FormattedTextControl( HTML('No layout specified. Press ENTER to quit.'), key_bindings=kb) window = Window(content=control, height=D(min=1)) return Layout(container=window, focused_element=window) prompt_toolkit-2.0.10/prompt_toolkit/layout/layout.py0000644000175100017510000003121113545407204024604 0ustar jonathanjonathan00000000000000""" Wrapper for the layout. """ from __future__ import unicode_literals import six from prompt_toolkit.buffer import Buffer from .containers import ConditionalContainer, Container, Window, to_container from .controls import BufferControl, UIControl __all__ = [ 'Layout', 'InvalidLayoutError', 'walk', ] class Layout(object): """ The layout for a prompt_toolkit :class:`~prompt_toolkit.application.Application`. This also keeps track of which user control is focused. :param container: The "root" container for the layout. :param focused_element: element to be focused initially. (Can be anything the `focus` function accepts.) """ def __init__(self, container, focused_element=None): self.container = to_container(container) self._stack = [] # Map search BufferControl back to the original BufferControl. # This is used to keep track of when exactly we are searching, and for # applying the search. # When a link exists in this dictionary, that means the search is # currently active. self.search_links = {} # search_buffer_control -> original buffer control. # Mapping that maps the children in the layout to their parent. # This relationship is calculated dynamically, each time when the UI # is rendered. (UI elements have only references to their children.) self._child_to_parent = {} if focused_element is None: try: self._stack.append(next(self.find_all_windows())) except StopIteration: raise InvalidLayoutError('Invalid layout. The layout does not contain any Window object.') else: self.focus(focused_element) # List of visible windows. self.visible_windows = [] # List of `Window` objects. def __repr__(self): return 'Layout(%r, current_window=%r)' % ( self.container, self.current_window) def find_all_windows(self): """ Find all the :class:`.UIControl` objects in this layout. """ for item in self.walk(): if isinstance(item, Window): yield item def find_all_controls(self): for container in self.find_all_windows(): yield container.content def focus(self, value): """ Focus the given UI element. `value` can be either: - a :class:`.UIControl` - a :class:`.Buffer` instance or the name of a :class:`.Buffer` - a :class:`.Window` - Any container object. In this case we will focus the :class:`.Window` from this container that was focused most recent, or the very first focusable :class:`.Window` of the container. """ # BufferControl by buffer name. if isinstance(value, six.text_type): for control in self.find_all_controls(): if isinstance(control, BufferControl) and control.buffer.name == value: self.focus(control) return raise ValueError("Couldn't find Buffer in the current layout: %r." % (value, )) # BufferControl by buffer object. elif isinstance(value, Buffer): for control in self.find_all_controls(): if isinstance(control, BufferControl) and control.buffer == value: self.focus(control) return raise ValueError("Couldn't find Buffer in the current layout: %r." % (value, )) # Focus UIControl. elif isinstance(value, UIControl): if value not in self.find_all_controls(): raise ValueError('Invalid value. Container does not appear in the layout.') if not value.is_focusable(): raise ValueError('Invalid value. UIControl is not focusable.') self.current_control = value # Otherwise, expecting any Container object. else: value = to_container(value) if isinstance(value, Window): # This is a `Window`: focus that. if value not in self.find_all_windows(): raise ValueError('Invalid value. Window does not appear in the layout: %r' % (value, )) self.current_window = value else: # Focus a window in this container. # If we have many windows as part of this container, and some # of them have been focused before, take the last focused # item. (This is very useful when the UI is composed of more # complex sub components.) windows = [] for c in walk(value, skip_hidden=True): if isinstance(c, Window) and c.content.is_focusable(): windows.append(c) # Take the first one that was focused before. for w in reversed(self._stack): if w in windows: self.current_window = w return # None was focused before: take the very first focusable window. if windows: self.current_window = windows[0] return raise ValueError('Invalid value. Container cannot be focused: %r' % (value, )) def has_focus(self, value): """ Check whether the given control has the focus. :param value: :class:`.UIControl` or :class:`.Window` instance. """ if isinstance(value, six.text_type): if self.current_buffer is None: return False return self.current_buffer.name == value if isinstance(value, Buffer): return self.current_buffer == value if isinstance(value, UIControl): return self.current_control == value else: value = to_container(value) if isinstance(value, Window): return self.current_window == value else: # Check whether this "container" is focused. This is true if # one of the elements inside is focused. for element in walk(value): if element == self.current_window: return True return False @property def current_control(self): """ Get the :class:`.UIControl` to currently has the focus. """ return self._stack[-1].content @current_control.setter def current_control(self, control): """ Set the :class:`.UIControl` to receive the focus. """ assert isinstance(control, UIControl) for window in self.find_all_windows(): if window.content == control: self.current_window = window return raise ValueError('Control not found in the user interface.') @property def current_window(self): " Return the :class:`.Window` object that is currently focused. " return self._stack[-1] @current_window.setter def current_window(self, value): " Set the :class:`.Window` object to be currently focused. " assert isinstance(value, Window) self._stack.append(value) @property def is_searching(self): " True if we are searching right now. " return self.current_control in self.search_links @property def search_target_buffer_control(self): " Return the :class:`.BufferControl` in which we are searching or `None`. " return self.search_links.get(self.current_control) def get_focusable_windows(self): """ Return all the :class:`.Window` objects which are focusable (in the 'modal' area). """ for w in self.walk_through_modal_area(): if isinstance(w, Window) and w.content.is_focusable(): yield w def get_visible_focusable_windows(self): """ Return a list of :class:`.Window` objects that are focusable. """ # focusable windows are windows that are visible, but also part of the # modal container. Make sure to keep the ordering. visible_windows = self.visible_windows return [w for w in self.get_focusable_windows() if w in visible_windows] @property def current_buffer(self): """ The currently focused :class:`~.Buffer` or `None`. """ ui_control = self.current_control if isinstance(ui_control, BufferControl): return ui_control.buffer def get_buffer_by_name(self, buffer_name): """ Look in the layout for a buffer with the given name. Return `None` when nothing was found. """ for w in self.walk(): if isinstance(w, Window) and isinstance(w.content, BufferControl): if w.content.buffer.name == buffer_name: return w.content.buffer @property def buffer_has_focus(self): """ Return `True` if the currently focused control is a :class:`.BufferControl`. (For instance, used to determine whether the default key bindings should be active or not.) """ ui_control = self.current_control return isinstance(ui_control, BufferControl) @property def previous_control(self): """ Get the :class:`.UIControl` to previously had the focus. """ try: return self._stack[-2].content except IndexError: return self._stack[-1].content def focus_last(self): """ Give the focus to the last focused control. """ if len(self._stack) > 1: self._stack = self._stack[:-1] def focus_next(self): """ Focus the next visible/focusable Window. """ windows = self.get_visible_focusable_windows() if len(windows) > 0: try: index = windows.index(self.current_window) except ValueError: index = 0 else: index = (index + 1) % len(windows) self.focus(windows[index]) def focus_previous(self): """ Focus the previous visible/focusable Window. """ windows = self.get_visible_focusable_windows() if len(windows) > 0: try: index = windows.index(self.current_window) except ValueError: index = 0 else: index = (index - 1) % len(windows) self.focus(windows[index]) def walk(self): """ Walk through all the layout nodes (and their children) and yield them. """ for i in walk(self.container): yield i def walk_through_modal_area(self): """ Walk through all the containers which are in the current 'modal' part of the layout. """ # Go up in the tree, and find the root. (it will be a part of the # layout, if the focus is in a modal part.) root = self.current_window while not root.is_modal() and root in self._child_to_parent: root = self._child_to_parent[root] for container in walk(root): yield container def update_parents_relations(self): """ Update child->parent relationships mapping. """ parents = {} def walk(e): for c in e.get_children(): parents[c] = e walk(c) walk(self.container) self._child_to_parent = parents def reset(self): # Remove all search links when the UI starts. # (Important, for instance when control-c is been pressed while # searching. The prompt cancels, but next `run()` call the search # links are still there.) self.search_links.clear() return self.container.reset() def get_parent(self, container): """ Return the parent container for the given container, or ``None``, if it wasn't found. """ try: return self._child_to_parent[container] except KeyError: return class InvalidLayoutError(Exception): pass def walk(container, skip_hidden=False): """ Walk through layout, starting at this container. """ assert isinstance(container, Container) # When `skip_hidden` is set, don't go into disabled ConditionalContainer containers. if skip_hidden and isinstance(container, ConditionalContainer) and not container.filter(): return yield container for c in container.get_children(): # yield from walk(c) for i in walk(c, skip_hidden=skip_hidden): yield i prompt_toolkit-2.0.10/prompt_toolkit/layout/margins.py0000644000175100017510000002222413545407204024733 0ustar jonathanjonathan00000000000000""" Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from six.moves import range from prompt_toolkit.filters import to_filter from prompt_toolkit.formatted_text import ( fragment_list_to_text, to_formatted_text, ) from prompt_toolkit.utils import get_cwidth __all__ = [ 'Margin', 'NumberedMargin', 'ScrollbarMargin', 'ConditionalMargin', 'PromptMargin', ] class Margin(with_metaclass(ABCMeta, object)): """ Base interface for a margin. """ @abstractmethod def get_width(self, get_ui_content): """ Return the width that this margin is going to consume. :param get_ui_content: Callable that asks the user control to create a :class:`.UIContent` instance. This can be used for instance to obtain the number of lines. """ return 0 @abstractmethod def create_margin(self, window_render_info, width, height): """ Creates a margin. This should return a list of (style_str, text) tuples. :param window_render_info: :class:`~prompt_toolkit.layout.containers.WindowRenderInfo` instance, generated after rendering and copying the visible part of the :class:`~prompt_toolkit.layout.controls.UIControl` into the :class:`~prompt_toolkit.layout.containers.Window`. :param width: The width that's available for this margin. (As reported by :meth:`.get_width`.) :param height: The height that's available for this margin. (The height of the :class:`~prompt_toolkit.layout.containers.Window`.) """ return [] class NumberedMargin(Margin): """ Margin that displays the line numbers. :param relative: Number relative to the cursor position. Similar to the Vi 'relativenumber' option. :param display_tildes: Display tildes after the end of the document, just like Vi does. """ def __init__(self, relative=False, display_tildes=False): self.relative = to_filter(relative) self.display_tildes = to_filter(display_tildes) def get_width(self, get_ui_content): line_count = get_ui_content().line_count return max(3, len('%s' % line_count) + 1) def create_margin(self, window_render_info, width, height): relative = self.relative() style = 'class:line-number' style_current = 'class:line-number,current-line-number' # Get current line number. current_lineno = window_render_info.ui_content.cursor_position.y # Construct margin. result = [] last_lineno = None for y, lineno in enumerate(window_render_info.displayed_lines): # Only display line number if this line is not a continuation of the previous line. if lineno != last_lineno: if lineno is None: pass elif lineno == current_lineno: # Current line. if relative: # Left align current number in relative mode. result.append((style_current, '%i' % (lineno + 1))) else: result.append((style_current, ('%i ' % (lineno + 1)).rjust(width))) else: # Other lines. if relative: lineno = abs(lineno - current_lineno) - 1 result.append((style, ('%i ' % (lineno + 1)).rjust(width))) last_lineno = lineno result.append(('', '\n')) # Fill with tildes. if self.display_tildes(): while y < window_render_info.window_height: result.append(('class:tilde', '~\n')) y += 1 return result class ConditionalMargin(Margin): """ Wrapper around other :class:`.Margin` classes to show/hide them. """ def __init__(self, margin, filter): assert isinstance(margin, Margin) self.margin = margin self.filter = to_filter(filter) def get_width(self, ui_content): if self.filter(): return self.margin.get_width(ui_content) else: return 0 def create_margin(self, window_render_info, width, height): if width and self.filter(): return self.margin.create_margin(window_render_info, width, height) else: return [] class ScrollbarMargin(Margin): """ Margin displaying a scrollbar. :param display_arrows: Display scroll up/down arrows. """ def __init__(self, display_arrows=False, up_arrow_symbol='^', down_arrow_symbol='v'): self.display_arrows = to_filter(display_arrows) self.up_arrow_symbol = up_arrow_symbol self.down_arrow_symbol = down_arrow_symbol def get_width(self, ui_content): return 1 def create_margin(self, window_render_info, width, height): content_height = window_render_info.content_height window_height = window_render_info.window_height display_arrows = self.display_arrows() if display_arrows: window_height -= 2 try: fraction_visible = len(window_render_info.displayed_lines) / float(content_height) fraction_above = window_render_info.vertical_scroll / float(content_height) scrollbar_height = int(min(window_height, max(1, window_height * fraction_visible))) scrollbar_top = int(window_height * fraction_above) except ZeroDivisionError: return [] else: def is_scroll_button(row): " True if we should display a button on this row. " return scrollbar_top <= row <= scrollbar_top + scrollbar_height # Up arrow. result = [] if display_arrows: result.extend([ ('class:scrollbar.arrow', self.up_arrow_symbol), ('class:scrollbar', '\n') ]) # Scrollbar body. scrollbar_background = 'class:scrollbar.background' scrollbar_background_start = 'class:scrollbar.background,scrollbar.start' scrollbar_button = 'class:scrollbar.button' scrollbar_button_end = 'class:scrollbar.button,scrollbar.end' for i in range(window_height): if is_scroll_button(i): if not is_scroll_button(i + 1): # Give the last cell a different style, because we # want to underline this. result.append((scrollbar_button_end, ' ')) else: result.append((scrollbar_button, ' ')) else: if is_scroll_button(i + 1): result.append((scrollbar_background_start, ' ')) else: result.append((scrollbar_background, ' ')) result.append(('', '\n')) # Down arrow if display_arrows: result.append(('class:scrollbar.arrow', self.down_arrow_symbol)) return result class PromptMargin(Margin): """ [Deprecated] Create margin that displays a prompt. This can display one prompt at the first line, and a continuation prompt (e.g, just dots) on all the following lines. This `PromptMargin` implementation has been largely superseded in favor of the `get_line_prefix` attribute of `Window`. The reason is that a margin is always a fixed width, while `get_line_prefix` can return a variable width prefix in front of every line, making it more powerful, especially for line continuations. :param get_prompt: Callable returns formatted text or a list of `(style_str, type)` tuples to be shown as the prompt at the first line. :param get_continuation: Callable that takes three inputs. The width (int), line_number (int), and is_soft_wrap (bool). It should return formatted text or a list of `(style_str, type)` tuples for the next lines of the input. """ def __init__(self, get_prompt, get_continuation=None): assert callable(get_prompt) assert get_continuation is None or callable(get_continuation) self.get_prompt = get_prompt self.get_continuation = get_continuation def get_width(self, ui_content): " Width to report to the `Window`. " # Take the width from the first line. text = fragment_list_to_text(self.get_prompt()) return get_cwidth(text) def create_margin(self, window_render_info, width, height): get_continuation = self.get_continuation result = [] # First line. result.extend(to_formatted_text(self.get_prompt())) # Next lines. if get_continuation: last_y = None for y in window_render_info.displayed_lines[1:]: result.append(('', '\n')) result.extend(to_formatted_text(get_continuation(width, y, y == last_y))) last_y = y return result prompt_toolkit-2.0.10/prompt_toolkit/layout/menus.py0000644000175100017510000005451413545407204024431 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import math from six.moves import range, zip_longest from prompt_toolkit.application.current import get_app from prompt_toolkit.filters import ( Condition, has_completions, is_done, to_filter, ) from prompt_toolkit.formatted_text import ( fragment_list_width, to_formatted_text, ) from prompt_toolkit.layout.utils import explode_text_fragments from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.utils import get_cwidth from .containers import ConditionalContainer, HSplit, ScrollOffsets, Window from .controls import UIContent, UIControl from .dimension import Dimension from .margins import ScrollbarMargin from .screen import Point __all__ = [ 'CompletionsMenu', 'MultiColumnCompletionsMenu', ] class CompletionsMenuControl(UIControl): """ Helper for drawing the complete menu to the screen. :param scroll_offset: Number (integer) representing the preferred amount of completions to be displayed before and after the current one. When this is a very high number, the current completion will be shown in the middle most of the time. """ # Preferred minimum size of the menu control. # The CompletionsMenu class defines a width of 8, and there is a scrollbar # of 1.) MIN_WIDTH = 7 def has_focus(self): return False def preferred_width(self, max_available_width): complete_state = get_app().current_buffer.complete_state if complete_state: menu_width = self._get_menu_width(500, complete_state) menu_meta_width = self._get_menu_meta_width(500, complete_state) return menu_width + menu_meta_width else: return 0 def preferred_height(self, width, max_available_height, wrap_lines, get_line_prefix): complete_state = get_app().current_buffer.complete_state if complete_state: return len(complete_state.completions) else: return 0 def create_content(self, width, height): """ Create a UIContent object for this control. """ complete_state = get_app().current_buffer.complete_state if complete_state: completions = complete_state.completions index = complete_state.complete_index # Can be None! # Calculate width of completions menu. menu_width = self._get_menu_width(width, complete_state) menu_meta_width = self._get_menu_meta_width(width - menu_width, complete_state) show_meta = self._show_meta(complete_state) def get_line(i): c = completions[i] is_current_completion = (i == index) result = _get_menu_item_fragments( c, is_current_completion, menu_width, space_after=True) if show_meta: result += self._get_menu_item_meta_fragments(c, is_current_completion, menu_meta_width) return result return UIContent(get_line=get_line, cursor_position=Point(x=0, y=index or 0), line_count=len(completions)) return UIContent() def _show_meta(self, complete_state): """ Return ``True`` if we need to show a column with meta information. """ return any(c.display_meta_text for c in complete_state.completions) def _get_menu_width(self, max_width, complete_state): """ Return the width of the main column. """ return min(max_width, max(self.MIN_WIDTH, max(get_cwidth(c.display_text) for c in complete_state.completions) + 2)) def _get_menu_meta_width(self, max_width, complete_state): """ Return the width of the meta column. """ def meta_width(completion): return get_cwidth(completion.display_meta_text) if self._show_meta(complete_state): return min( max_width, max(meta_width(c) for c in complete_state.completions) + 2) else: return 0 def _get_menu_item_meta_fragments(self, completion, is_current_completion, width): if is_current_completion: style_str = 'class:completion-menu.meta.completion.current' else: style_str = 'class:completion-menu.meta.completion' text, tw = _trim_formatted_text(completion.display_meta, width - 2) padding = ' ' * (width - 1 - tw) return to_formatted_text( [('', ' ')] + text + [('', padding)], style=style_str) def mouse_handler(self, mouse_event): """ Handle mouse events: clicking and scrolling. """ b = get_app().current_buffer if mouse_event.event_type == MouseEventType.MOUSE_UP: # Select completion. b.go_to_completion(mouse_event.position.y) b.complete_state = None elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: # Scroll up. b.complete_next(count=3, disable_wrap_around=True) elif mouse_event.event_type == MouseEventType.SCROLL_UP: # Scroll down. b.complete_previous(count=3, disable_wrap_around=True) def _get_menu_item_fragments( completion, is_current_completion, width, space_after=False): """ Get the style/text tuples for a menu item, styled and trimmed to the given width. """ if is_current_completion: style_str = 'class:completion-menu.completion.current %s %s' % ( completion.style, completion.selected_style) else: style_str = 'class:completion-menu.completion ' + completion.style text, tw = _trim_formatted_text( completion.display, (width - 2 if space_after else width - 1)) padding = ' ' * (width - 1 - tw) return to_formatted_text( [('', ' ')] + text + [('', padding)], style=style_str) def _trim_formatted_text(formatted_text, max_width): """ Trim the text to `max_width`, append dots when the text is too long. Returns (text, width) tuple. """ width = fragment_list_width(formatted_text) # When the text is too wide, trim it. if width > max_width: result = [] # Text fragments. remaining_width = max_width - 3 for style_and_ch in explode_text_fragments(formatted_text): ch_width = get_cwidth(style_and_ch[1]) if ch_width <= remaining_width: result.append(style_and_ch) remaining_width -= ch_width else: break result.append(('', '...')) return result, max_width - remaining_width else: return formatted_text, width class CompletionsMenu(ConditionalContainer): # NOTE: We use a pretty big z_index by default. Menus are supposed to be # above anything else. We also want to make sure that the content is # visible at the point where we draw this menu. def __init__(self, max_height=None, scroll_offset=0, extra_filter=True, display_arrows=False, z_index=10 ** 8): extra_filter = to_filter(extra_filter) display_arrows = to_filter(display_arrows) super(CompletionsMenu, self).__init__( content=Window( content=CompletionsMenuControl(), width=Dimension(min=8), height=Dimension(min=1, max=max_height), scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset), right_margins=[ScrollbarMargin(display_arrows=display_arrows)], dont_extend_width=True, style='class:completion-menu', z_index=z_index, ), # Show when there are completions but not at the point we are # returning the input. filter=has_completions & ~is_done & extra_filter) class MultiColumnCompletionMenuControl(UIControl): """ Completion menu that displays all the completions in several columns. When there are more completions than space for them to be displayed, an arrow is shown on the left or right side. `min_rows` indicates how many rows will be available in any possible case. When this is larger than one, it will try to use less columns and more rows until this value is reached. Be careful passing in a too big value, if less than the given amount of rows are available, more columns would have been required, but `preferred_width` doesn't know about that and reports a too small value. This results in less completions displayed and additional scrolling. (It's a limitation of how the layout engine currently works: first the widths are calculated, then the heights.) :param suggested_max_column_width: The suggested max width of a column. The column can still be bigger than this, but if there is place for two columns of this width, we will display two columns. This to avoid that if there is one very wide completion, that it doesn't significantly reduce the amount of columns. """ _required_margin = 3 # One extra padding on the right + space for arrows. def __init__(self, min_rows=3, suggested_max_column_width=30): assert isinstance(min_rows, int) and min_rows >= 1 self.min_rows = min_rows self.suggested_max_column_width = suggested_max_column_width self.scroll = 0 # Info of last rendering. self._rendered_rows = 0 self._rendered_columns = 0 self._total_columns = 0 self._render_pos_to_completion = {} self._render_left_arrow = False self._render_right_arrow = False self._render_width = 0 def reset(self): self.scroll = 0 def has_focus(self): return False def preferred_width(self, max_available_width): """ Preferred width: prefer to use at least min_rows, but otherwise as much as possible horizontally. """ complete_state = get_app().current_buffer.complete_state column_width = self._get_column_width(complete_state) result = int(column_width * math.ceil(len(complete_state.completions) / float(self.min_rows))) # When the desired width is still more than the maximum available, # reduce by removing columns until we are less than the available # width. while result > column_width and result > max_available_width - self._required_margin: result -= column_width return result + self._required_margin def preferred_height(self, width, max_available_height, wrap_lines, get_line_prefix): """ Preferred height: as much as needed in order to display all the completions. """ complete_state = get_app().current_buffer.complete_state column_width = self._get_column_width(complete_state) column_count = max(1, (width - self._required_margin) // column_width) return int(math.ceil(len(complete_state.completions) / float(column_count))) def create_content(self, width, height): """ Create a UIContent object for this menu. """ complete_state = get_app().current_buffer.complete_state column_width = self._get_column_width(complete_state) self._render_pos_to_completion = {} def grouper(n, iterable, fillvalue=None): " grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx " args = [iter(iterable)] * n return zip_longest(fillvalue=fillvalue, *args) def is_current_completion(completion): " Returns True when this completion is the currently selected one. " return complete_state.complete_index is not None and c == complete_state.current_completion # Space required outside of the regular columns, for displaying the # left and right arrow. HORIZONTAL_MARGIN_REQUIRED = 3 if complete_state: # There should be at least one column, but it cannot be wider than # the available width. column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) # However, when the columns tend to be very wide, because there are # some very wide entries, shrink it anyway. if column_width > self.suggested_max_column_width: # `column_width` can still be bigger that `suggested_max_column_width`, # but if there is place for two columns, we divide by two. column_width //= (column_width // self.suggested_max_column_width) visible_columns = max(1, (width - self._required_margin) // column_width) columns_ = list(grouper(height, complete_state.completions)) rows_ = list(zip(*columns_)) # Make sure the current completion is always visible: update scroll offset. selected_column = (complete_state.complete_index or 0) // height self.scroll = min(selected_column, max(self.scroll, selected_column - visible_columns + 1)) render_left_arrow = self.scroll > 0 render_right_arrow = self.scroll < len(rows_[0]) - visible_columns # Write completions to screen. fragments_for_line = [] for row_index, row in enumerate(rows_): fragments = [] middle_row = row_index == len(rows_) // 2 # Draw left arrow if we have hidden completions on the left. if render_left_arrow: fragments.append(('class:scrollbar', '<' if middle_row else ' ')) elif render_right_arrow: # Reserve one column empty space. (If there is a right # arrow right now, there can be a left arrow as well.) fragments.append(('', ' ')) # Draw row content. for column_index, c in enumerate(row[self.scroll:][:visible_columns]): if c is not None: fragments += _get_menu_item_fragments( c, is_current_completion(c), column_width, space_after=False) # Remember render position for mouse click handler. for x in range(column_width): self._render_pos_to_completion[(column_index * column_width + x, row_index)] = c else: fragments.append(('class:completion', ' ' * column_width)) # Draw trailing padding for this row. # (_get_menu_item_fragments only returns padding on the left.) if render_left_arrow or render_right_arrow: fragments.append(('class:completion', ' ')) # Draw right arrow if we have hidden completions on the right. if render_right_arrow: fragments.append(('class:scrollbar', '>' if middle_row else ' ')) elif render_left_arrow: fragments.append(('class:completion', ' ')) # Add line. fragments_for_line.append(to_formatted_text( fragments, style='class:completion-menu')) else: fragments = [] self._rendered_rows = height self._rendered_columns = visible_columns self._total_columns = len(columns_) self._render_left_arrow = render_left_arrow self._render_right_arrow = render_right_arrow self._render_width = column_width * visible_columns + render_left_arrow + render_right_arrow + 1 def get_line(i): return fragments_for_line[i] return UIContent(get_line=get_line, line_count=len(rows_)) def _get_column_width(self, complete_state): """ Return the width of each column. """ return max(get_cwidth(c.display_text) for c in complete_state.completions) + 1 def mouse_handler(self, mouse_event): """ Handle scroll and click events. """ b = get_app().current_buffer def scroll_left(): b.complete_previous(count=self._rendered_rows, disable_wrap_around=True) self.scroll = max(0, self.scroll - 1) def scroll_right(): b.complete_next(count=self._rendered_rows, disable_wrap_around=True) self.scroll = min(self._total_columns - self._rendered_columns, self.scroll + 1) if mouse_event.event_type == MouseEventType.SCROLL_DOWN: scroll_right() elif mouse_event.event_type == MouseEventType.SCROLL_UP: scroll_left() elif mouse_event.event_type == MouseEventType.MOUSE_UP: x = mouse_event.position.x y = mouse_event.position.y # Mouse click on left arrow. if x == 0: if self._render_left_arrow: scroll_left() # Mouse click on right arrow. elif x == self._render_width - 1: if self._render_right_arrow: scroll_right() # Mouse click on completion. else: completion = self._render_pos_to_completion.get((x, y)) if completion: b.apply_completion(completion) def get_key_bindings(self): """ Expose key bindings that handle the left/right arrow keys when the menu is displayed. """ from prompt_toolkit.key_binding.key_bindings import KeyBindings kb = KeyBindings() @Condition def filter(): " Only handle key bindings if this menu is visible. " app = get_app() complete_state = app.current_buffer.complete_state # There need to be completions, and one needs to be selected. if complete_state is None or complete_state.complete_index is None: return False # This menu needs to be visible. return any( window.content == self for window in app.layout.visible_windows) def move(right=False): buff = get_app().current_buffer complete_state = buff.complete_state if complete_state is not None and \ buff.complete_state.complete_index is not None: # Calculate new complete index. new_index = buff.complete_state.complete_index if right: new_index += self._rendered_rows else: new_index -= self._rendered_rows if 0 <= new_index < len(complete_state.completions): buff.go_to_completion(new_index) # NOTE: the is_global is required because the completion menu will # never be focussed. @kb.add('left', is_global=True, filter=filter) def _(event): move() @kb.add('right', is_global=True, filter=filter) def _(event): move(True) return kb class MultiColumnCompletionsMenu(HSplit): """ Container that displays the completions in several columns. When `show_meta` (a :class:`~prompt_toolkit.filters.Filter`) evaluates to True, it shows the meta information at the bottom. """ def __init__(self, min_rows=3, suggested_max_column_width=30, show_meta=True, extra_filter=True, z_index=10 ** 8): show_meta = to_filter(show_meta) extra_filter = to_filter(extra_filter) # Display filter: show when there are completions but not at the point # we are returning the input. full_filter = has_completions & ~is_done & extra_filter @Condition def any_completion_has_meta(): return any(c.display_meta for c in get_app().current_buffer.complete_state.completions) # Create child windows. # NOTE: We don't set style='class:completion-menu' to the # `MultiColumnCompletionMenuControl`, because this is used in a # Float that is made transparent, and the size of the control # doesn't always correspond exactly with the size of the # generated content. completions_window = ConditionalContainer( content=Window( content=MultiColumnCompletionMenuControl( min_rows=min_rows, suggested_max_column_width=suggested_max_column_width), width=Dimension(min=8), height=Dimension(min=1)), filter=full_filter) meta_window = ConditionalContainer( content=Window(content=_SelectedCompletionMetaControl()), filter=show_meta & full_filter & any_completion_has_meta) # Initialise split. super(MultiColumnCompletionsMenu, self).__init__([ completions_window, meta_window ], z_index=z_index) class _SelectedCompletionMetaControl(UIControl): """ Control that shows the meta information of the selected completion. """ def preferred_width(self, max_available_width): """ Report the width of the longest meta text as the preferred width of this control. It could be that we use less width, but this way, we're sure that the layout doesn't change when we select another completion (E.g. that completions are suddenly shown in more or fewer columns.) """ app = get_app() if app.current_buffer.complete_state: state = app.current_buffer.complete_state return 2 + max(get_cwidth(c.display_meta_text) for c in state.completions) else: return 0 def preferred_height(self, width, max_available_height, wrap_lines, get_line_prefix): return 1 def create_content(self, width, height): fragments = self._get_text_fragments() def get_line(i): return fragments return UIContent(get_line=get_line, line_count=1 if fragments else 0) def _get_text_fragments(self): style = 'class:completion-menu.multi-column-meta' state = get_app().current_buffer.complete_state if state and state.current_completion and state.current_completion.display_meta_text: return to_formatted_text( [('', ' ')] + state.current_completion.display_meta + [('', ' ')], style=style) return [] prompt_toolkit-2.0.10/prompt_toolkit/layout/mouse_handlers.py0000644000175100017510000000141013545407204026275 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from collections import defaultdict from itertools import product __all__ = [ 'MouseHandlers', ] class MouseHandlers(object): """ Two dimensional raster of callbacks for mouse events. """ def __init__(self): def dummy_callback(mouse_event): """ :param mouse_event: `MouseEvent` instance. """ # Map (x,y) tuples to handlers. self.mouse_handlers = defaultdict(lambda: dummy_callback) def set_mouse_handler_for_range(self, x_min, x_max, y_min, y_max, handler=None): """ Set mouse handler for a region. """ for x, y in product(range(x_min, x_max), range(y_min, y_max)): self.mouse_handlers[x, y] = handler prompt_toolkit-2.0.10/prompt_toolkit/layout/processors.py0000644000175100017510000007322213545407204025501 0ustar jonathanjonathan00000000000000""" Processors are little transformation blocks that transform the fragments list from a buffer before the BufferControl will render it to the screen. They can insert fragments before or after, or highlight fragments by replacing the fragment types. """ from __future__ import unicode_literals import re from abc import ABCMeta, abstractmethod from six import text_type, with_metaclass from six.moves import range from prompt_toolkit.application.current import get_app from prompt_toolkit.cache import SimpleCache from prompt_toolkit.document import Document from prompt_toolkit.filters import to_filter, vi_insert_multiple_mode from prompt_toolkit.formatted_text import to_formatted_text from prompt_toolkit.formatted_text.utils import ( fragment_list_len, fragment_list_to_text, ) from prompt_toolkit.search import SearchDirection from prompt_toolkit.utils import to_int, to_str from .utils import explode_text_fragments __all__ = [ 'Processor', 'TransformationInput', 'Transformation', 'DummyProcessor', 'HighlightSearchProcessor', 'HighlightIncrementalSearchProcessor', 'HighlightSelectionProcessor', 'PasswordProcessor', 'HighlightMatchingBracketProcessor', 'DisplayMultipleCursors', 'BeforeInput', 'ShowArg', 'AfterInput', 'AppendAutoSuggestion', 'ConditionalProcessor', 'ShowLeadingWhiteSpaceProcessor', 'ShowTrailingWhiteSpaceProcessor', 'TabsProcessor', 'ReverseSearchProcessor', 'DynamicProcessor', 'merge_processors', ] class Processor(with_metaclass(ABCMeta, object)): """ Manipulate the fragments for a given line in a :class:`~prompt_toolkit.layout.controls.BufferControl`. """ @abstractmethod def apply_transformation(self, transformation_input): """ Apply transformation. Returns a :class:`.Transformation` instance. :param transformation_input: :class:`.TransformationInput` object. """ return Transformation(transformation_input.fragments) class TransformationInput(object): """ :param control: :class:`.BufferControl` instance. :param lineno: The number of the line to which we apply the processor. :param source_to_display: A function that returns the position in the `fragments` for any position in the source string. (This takes previous processors into account.) :param fragments: List of fragments that we can transform. (Received from the previous processor.) """ def __init__(self, buffer_control, document, lineno, source_to_display, fragments, width, height): self.buffer_control = buffer_control self.document = document self.lineno = lineno self.source_to_display = source_to_display self.fragments = fragments self.width = width self.height = height def unpack(self): return (self.buffer_control, self.document, self.lineno, self.source_to_display, self.fragments, self.width, self.height) class Transformation(object): """ Transformation result, as returned by :meth:`.Processor.apply_transformation`. Important: Always make sure that the length of `document.text` is equal to the length of all the text in `fragments`! :param fragments: The transformed fragments. To be displayed, or to pass to the next processor. :param source_to_display: Cursor position transformation from original string to transformed string. :param display_to_source: Cursor position transformed from source string to original string. """ def __init__(self, fragments, source_to_display=None, display_to_source=None): self.fragments = fragments self.source_to_display = source_to_display or (lambda i: i) self.display_to_source = display_to_source or (lambda i: i) class DummyProcessor(Processor): """ A `Processor` that doesn't do anything. """ def apply_transformation(self, transformation_input): return Transformation(transformation_input.fragments) class HighlightSearchProcessor(Processor): """ Processor that highlights search matches in the document. Note that this doesn't support multiline search matches yet. The style classes 'search' and 'search.current' will be applied to the content. """ _classname = 'search' _classname_current = 'search.current' def _get_search_text(self, buffer_control): """ The text we are searching for. """ return buffer_control.search_state.text def apply_transformation(self, transformation_input): buffer_control, document, lineno, source_to_display, fragments, _, _ = transformation_input.unpack() search_text = self._get_search_text(buffer_control) searchmatch_fragment = ' class:%s ' % (self._classname, ) searchmatch_current_fragment = ' class:%s ' % (self._classname_current, ) if search_text and not get_app().is_done: # For each search match, replace the style string. line_text = fragment_list_to_text(fragments) fragments = explode_text_fragments(fragments) if buffer_control.search_state.ignore_case(): flags = re.IGNORECASE else: flags = 0 # Get cursor column. if document.cursor_position_row == lineno: cursor_column = source_to_display(document.cursor_position_col) else: cursor_column = None for match in re.finditer(re.escape(search_text), line_text, flags=flags): if cursor_column is not None: on_cursor = match.start() <= cursor_column < match.end() else: on_cursor = False for i in range(match.start(), match.end()): old_fragment, text = fragments[i] if on_cursor: fragments[i] = (old_fragment + searchmatch_current_fragment, fragments[i][1]) else: fragments[i] = (old_fragment + searchmatch_fragment, fragments[i][1]) return Transformation(fragments) class HighlightIncrementalSearchProcessor(HighlightSearchProcessor): """ Highlight the search terms that are used for highlighting the incremental search. The style class 'incsearch' will be applied to the content. Important: this requires the `preview_search=True` flag to be set for the `BufferControl`. Otherwise, the cursor position won't be set to the search match while searching, and nothing happens. """ _classname = 'incsearch' _classname_current = 'incsearch.current' def _get_search_text(self, buffer_control): """ The text we are searching for. """ # When the search buffer has focus, take that text. search_buffer = buffer_control.search_buffer if search_buffer is not None and search_buffer.text: return search_buffer.text class HighlightSelectionProcessor(Processor): """ Processor that highlights the selection in the document. """ def apply_transformation(self, transformation_input): buffer_control, document, lineno, source_to_display, fragments, _, _ = transformation_input.unpack() selected_fragment = ' class:selected ' # In case of selection, highlight all matches. selection_at_line = document.selection_range_at_line(lineno) if selection_at_line: from_, to = selection_at_line from_ = source_to_display(from_) to = source_to_display(to) fragments = explode_text_fragments(fragments) if from_ == 0 and to == 0 and len(fragments) == 0: # When this is an empty line, insert a space in order to # visualise the selection. return Transformation([(selected_fragment, ' ')]) else: for i in range(from_, to): if i < len(fragments): old_fragment, old_text = fragments[i] fragments[i] = (old_fragment + selected_fragment, old_text) elif i == len(fragments): fragments.append((selected_fragment, ' ')) return Transformation(fragments) class PasswordProcessor(Processor): """ Processor that turns masks the input. (For passwords.) :param char: (string) Character to be used. "*" by default. """ def __init__(self, char='*'): self.char = char def apply_transformation(self, ti): fragments = [(style, self.char * len(text)) for style, text in ti.fragments] return Transformation(fragments) class HighlightMatchingBracketProcessor(Processor): """ When the cursor is on or right after a bracket, it highlights the matching bracket. :param max_cursor_distance: Only highlight matching brackets when the cursor is within this distance. (From inside a `Processor`, we can't know which lines will be visible on the screen. But we also don't want to scan the whole document for matching brackets on each key press, so we limit to this value.) """ _closing_braces = '])}>' def __init__(self, chars='[](){}<>', max_cursor_distance=1000): self.chars = chars self.max_cursor_distance = max_cursor_distance self._positions_cache = SimpleCache(maxsize=8) def _get_positions_to_highlight(self, document): """ Return a list of (row, col) tuples that need to be highlighted. """ # Try for the character under the cursor. if document.current_char and document.current_char in self.chars: pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) # Try for the character before the cursor. elif (document.char_before_cursor and document.char_before_cursor in self._closing_braces and document.char_before_cursor in self.chars): document = Document(document.text, document.cursor_position - 1) pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) else: pos = None # Return a list of (row, col) tuples that need to be highlighted. if pos: pos += document.cursor_position # pos is relative. row, col = document.translate_index_to_position(pos) return [(row, col), (document.cursor_position_row, document.cursor_position_col)] else: return [] def apply_transformation(self, transformation_input): buffer_control, document, lineno, source_to_display, fragments, _, _ = transformation_input.unpack() # When the application is in the 'done' state, don't highlight. if get_app().is_done: return Transformation(fragments) # Get the highlight positions. key = (get_app().render_counter, document.text, document.cursor_position) positions = self._positions_cache.get( key, lambda: self._get_positions_to_highlight(document)) # Apply if positions were found at this line. if positions: for row, col in positions: if row == lineno: col = source_to_display(col) fragments = explode_text_fragments(fragments) style, text = fragments[col] if col == document.cursor_position_col: style += ' class:matching-bracket.cursor ' else: style += ' class:matching-bracket.other ' fragments[col] = (style, text) return Transformation(fragments) class DisplayMultipleCursors(Processor): """ When we're in Vi block insert mode, display all the cursors. """ def apply_transformation(self, transformation_input): buffer_control, document, lineno, source_to_display, fragments, _, _ = transformation_input.unpack() buff = buffer_control.buffer if vi_insert_multiple_mode(): cursor_positions = buff.multiple_cursor_positions fragments = explode_text_fragments(fragments) # If any cursor appears on the current line, highlight that. start_pos = document.translate_row_col_to_index(lineno, 0) end_pos = start_pos + len(document.lines[lineno]) fragment_suffix = ' class:multiple-cursors' for p in cursor_positions: if start_pos <= p <= end_pos: column = source_to_display(p - start_pos) # Replace fragment. try: style, text = fragments[column] except IndexError: # Cursor needs to be displayed after the current text. fragments.append((fragment_suffix, ' ')) else: style += fragment_suffix fragments[column] = (style, text) return Transformation(fragments) else: return Transformation(fragments) class BeforeInput(Processor): """ Insert text before the input. :param text: This can be either plain text or formatted text (or a callable that returns any of those). :param style: style to be applied to this prompt/prefix. """ def __init__(self, text, style=''): self.text = text self.style = style def apply_transformation(self, ti): if ti.lineno == 0: # Get fragments. fragments_before = to_formatted_text(self.text, self.style) fragments = fragments_before + ti.fragments shift_position = fragment_list_len(fragments_before) source_to_display = lambda i: i + shift_position display_to_source = lambda i: i - shift_position else: fragments = ti.fragments source_to_display = None display_to_source = None return Transformation(fragments, source_to_display=source_to_display, display_to_source=display_to_source) def __repr__(self): return 'BeforeInput(%r, %r)' % (self.text, self.style) class ShowArg(BeforeInput): """ Display the 'arg' in front of the input. This was used by the `PromptSession`, but now it uses the `Window.get_line_prefix` function instead. """ def __init__(self): super(ShowArg, self).__init__(self._get_text_fragments) def _get_text_fragments(self): app = get_app() if app.key_processor.arg is None: return [] else: arg = app.key_processor.arg return [ ('class:prompt.arg', '(arg: '), ('class:prompt.arg.text', str(arg)), ('class:prompt.arg', ') '), ] def __repr__(self): return 'ShowArg()' class AfterInput(Processor): """ Insert text after the input. :param text: This can be either plain text or formatted text (or a callable that returns any of those). :param style: style to be applied to this prompt/prefix. """ def __init__(self, text, style=''): self.text = text self.style = style def apply_transformation(self, ti): # Insert fragments after the last line. if ti.lineno == ti.document.line_count - 1: # Get fragments. fragments_after = to_formatted_text(self.text, self.style) return Transformation(fragments=ti.fragments + fragments_after) else: return Transformation(fragments=ti.fragments) def __repr__(self): return '%s(%r, style=%r)' % ( self.__class__.__name__, self.text, self.style) class AppendAutoSuggestion(Processor): """ Append the auto suggestion to the input. (The user can then press the right arrow the insert the suggestion.) """ def __init__(self, style='class:auto-suggestion'): self.style = style def apply_transformation(self, ti): # Insert fragments after the last line. if ti.lineno == ti.document.line_count - 1: buffer = ti.buffer_control.buffer if buffer.suggestion and ti.document.is_cursor_at_the_end: suggestion = buffer.suggestion.text else: suggestion = '' return Transformation(fragments=ti.fragments + [(self.style, suggestion)]) else: return Transformation(fragments=ti.fragments) class ShowLeadingWhiteSpaceProcessor(Processor): """ Make leading whitespace visible. :param get_char: Callable that returns one character. """ def __init__(self, get_char=None, style='class:leading-whitespace'): assert get_char is None or callable(get_char) if get_char is None: def get_char(): if '\xb7'.encode(get_app().output.encoding(), 'replace') == b'?': return '.' else: return '\xb7' self.style = style self.get_char = get_char def apply_transformation(self, ti): fragments = ti.fragments # Walk through all te fragments. if fragments and fragment_list_to_text(fragments).startswith(' '): t = (self.style, self.get_char()) fragments = explode_text_fragments(fragments) for i in range(len(fragments)): if fragments[i][1] == ' ': fragments[i] = t else: break return Transformation(fragments) class ShowTrailingWhiteSpaceProcessor(Processor): """ Make trailing whitespace visible. :param get_char: Callable that returns one character. """ def __init__(self, get_char=None, style='class:training-whitespace'): assert get_char is None or callable(get_char) if get_char is None: def get_char(): if '\xb7'.encode(get_app().output.encoding(), 'replace') == b'?': return '.' else: return '\xb7' self.style = style self.get_char = get_char def apply_transformation(self, ti): fragments = ti.fragments if fragments and fragments[-1][1].endswith(' '): t = (self.style, self.get_char()) fragments = explode_text_fragments(fragments) # Walk backwards through all te fragments and replace whitespace. for i in range(len(fragments) - 1, -1, -1): char = fragments[i][1] if char == ' ': fragments[i] = t else: break return Transformation(fragments) class TabsProcessor(Processor): """ Render tabs as spaces (instead of ^I) or make them visible (for instance, by replacing them with dots.) :param tabstop: Horizontal space taken by a tab. (`int` or callable that returns an `int`). :param char1: Character or callable that returns a character (text of length one). This one is used for the first space taken by the tab. :param char2: Like `char1`, but for the rest of the space. """ def __init__(self, tabstop=4, char1='|', char2='\u2508', style='class:tab'): assert isinstance(tabstop, int) or callable(tabstop) assert callable(char1) or isinstance(char1, text_type) assert callable(char2) or isinstance(char2, text_type) self.char1 = char1 self.char2 = char2 self.tabstop = tabstop self.style = style def apply_transformation(self, ti): tabstop = to_int(self.tabstop) style = self.style # Create separator for tabs. separator1 = to_str(self.char1) separator2 = to_str(self.char2) # Transform fragments. fragments = explode_text_fragments(ti.fragments) position_mappings = {} result_fragments = [] pos = 0 for i, fragment_and_text in enumerate(fragments): position_mappings[i] = pos if fragment_and_text[1] == '\t': # Calculate how many characters we have to insert. count = tabstop - (pos % tabstop) if count == 0: count = tabstop # Insert tab. result_fragments.append((style, separator1)) result_fragments.append((style, separator2 * (count - 1))) pos += count else: result_fragments.append(fragment_and_text) pos += 1 position_mappings[len(fragments)] = pos # Add `pos+1` to mapping, because the cursor can be right after the # line as well. position_mappings[len(fragments) + 1] = pos + 1 def source_to_display(from_position): " Maps original cursor position to the new one. " return position_mappings[from_position] def display_to_source(display_pos): " Maps display cursor position to the original one. " position_mappings_reversed = dict((v, k) for k, v in position_mappings.items()) while display_pos >= 0: try: return position_mappings_reversed[display_pos] except KeyError: display_pos -= 1 return 0 return Transformation( result_fragments, source_to_display=source_to_display, display_to_source=display_to_source) class ReverseSearchProcessor(Processor): """ Process to display the "(reverse-i-search)`...`:..." stuff around the search buffer. Note: This processor is meant to be applied to the BufferControl that contains the search buffer, it's not meant for the original input. """ _excluded_input_processors = [ HighlightSearchProcessor, HighlightSelectionProcessor, BeforeInput, AfterInput, ] def _get_main_buffer(self, buffer_control): from prompt_toolkit.layout.controls import BufferControl prev_control = get_app().layout.search_target_buffer_control if isinstance(prev_control, BufferControl) and \ prev_control.search_buffer_control == buffer_control: return prev_control return None def _content(self, main_control, ti): from prompt_toolkit.layout.controls import BufferControl # Emulate the BufferControl through which we are searching. # For this we filter out some of the input processors. excluded_processors = tuple(self._excluded_input_processors) def filter_processor(item): """ Filter processors from the main control that we want to disable here. This returns either an accepted processor or None. """ # For a `_MergedProcessor`, check each individual processor, recursively. if isinstance(item, _MergedProcessor): accepted_processors = [filter_processor(p) for p in item.processors] accepted_processors = [p for p in accepted_processors if p is not None] if len(accepted_processors) > 1: return _MergedProcessor(accepted_processors) elif accepted_processors == 1: return accepted_processors[0] # For a `ConditionalProcessor`, check the body. elif isinstance(item, ConditionalProcessor): p = filter_processor(item.processor) if p: return ConditionalProcessor(p, item.filter) # Otherwise, check the processor itself. else: if not isinstance(item, excluded_processors): return item filtered_processor = filter_processor( merge_processors(main_control.input_processors or [])) highlight_processor = HighlightIncrementalSearchProcessor() if filtered_processor: new_processors = [filtered_processor, highlight_processor] else: new_processors = [highlight_processor] buffer_control = BufferControl( buffer=main_control.buffer, input_processors=new_processors, include_default_input_processors=False, lexer=main_control.lexer, preview_search=True, search_buffer_control=ti.buffer_control) return buffer_control.create_content(ti.width, ti.height, preview_search=True) def apply_transformation(self, ti): main_control = self._get_main_buffer(ti.buffer_control) if ti.lineno == 0 and main_control: content = self._content(main_control, ti) # Get the line from the original document for this search. line_fragments = content.get_line(content.cursor_position.y) if main_control.search_state.direction == SearchDirection.FORWARD: direction_text = 'i-search' else: direction_text = 'reverse-i-search' fragments_before = [ ('class:prompt.search', '('), ('class:prompt.search', direction_text), ('class:prompt.search', ')`'), ] fragments = fragments_before + [ ('class:prompt.search.text', fragment_list_to_text(ti.fragments)), ('', "': "), ] + line_fragments shift_position = fragment_list_len(fragments_before) source_to_display = lambda i: i + shift_position display_to_source = lambda i: i - shift_position else: source_to_display = None display_to_source = None fragments = ti.fragments return Transformation(fragments, source_to_display=source_to_display, display_to_source=display_to_source) class ConditionalProcessor(Processor): """ Processor that applies another processor, according to a certain condition. Example:: # Create a function that returns whether or not the processor should # currently be applied. def highlight_enabled(): return true_or_false # Wrapped it in a `ConditionalProcessor` for usage in a `BufferControl`. BufferControl(input_processors=[ ConditionalProcessor(HighlightSearchProcessor(), Condition(highlight_enabled))]) :param processor: :class:`.Processor` instance. :param filter: :class:`~prompt_toolkit.filters.Filter` instance. """ def __init__(self, processor, filter): assert isinstance(processor, Processor) self.processor = processor self.filter = to_filter(filter) def apply_transformation(self, transformation_input): # Run processor when enabled. if self.filter(): return self.processor.apply_transformation(transformation_input) else: return Transformation(transformation_input.fragments) def __repr__(self): return '%s(processor=%r, filter=%r)' % ( self.__class__.__name__, self.processor, self.filter) class DynamicProcessor(Processor): """ Processor class that can dynamically returns any Processor. :param get_processor: Callable that returns a :class:`.Processor` instance. """ def __init__(self, get_processor): assert callable(get_processor) self.get_processor = get_processor def apply_transformation(self, ti): processor = self.get_processor() or DummyProcessor() return processor.apply_transformation(ti) def merge_processors(processors): """ Merge multiple `Processor` objects into one. """ return _MergedProcessor(processors) class _MergedProcessor(Processor): """ Processor that groups multiple other `Processor` objects, but exposes an API as if it is one `Processor`. """ def __init__(self, processors): assert all(isinstance(p, Processor) for p in processors) self.processors = processors def apply_transformation(self, ti): source_to_display_functions = [ti.source_to_display] display_to_source_functions = [] fragments = ti.fragments def source_to_display(i): """ Translate x position from the buffer to the x position in the processor fragments list. """ for f in source_to_display_functions: i = f(i) return i for p in self.processors: transformation = p.apply_transformation(TransformationInput( ti.buffer_control, ti.document, ti.lineno, source_to_display, fragments, ti.width, ti.height)) fragments = transformation.fragments display_to_source_functions.append(transformation.display_to_source) source_to_display_functions.append(transformation.source_to_display) def display_to_source(i): for f in reversed(display_to_source_functions): i = f(i) return i # In the case of a nested _MergedProcessor, each processor wants to # receive a 'source_to_display' function (as part of the # TransformationInput) that has everything in the chain before # included, because it can be called as part of the # `apply_transformation` function. However, this first # `source_to_display` should not be part of the output that we are # returning. (This is the most consistent with `display_to_source`.) del source_to_display_functions[:1] return Transformation(fragments, source_to_display, display_to_source) prompt_toolkit-2.0.10/prompt_toolkit/layout/screen.py0000644000175100017510000002135713545407204024560 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from collections import defaultdict, namedtuple from prompt_toolkit.cache import FastDictCache from prompt_toolkit.utils import get_cwidth __all__ = [ 'Point', 'Size', 'Screen', 'Char', ] Point = namedtuple('Point', 'x y') Size = namedtuple('Size', 'rows columns') class Char(object): """ Represent a single character in a :class:`.Screen`. This should be considered immutable. :param char: A single character (can be a double-width character). :param style: A style string. (Can contain classnames.) """ __slots__ = ('char', 'style', 'width') # If we end up having one of these special control sequences in the input string, # we should display them as follows: # Usually this happens after a "quoted insert". display_mappings = { '\x00': '^@', # Control space '\x01': '^A', '\x02': '^B', '\x03': '^C', '\x04': '^D', '\x05': '^E', '\x06': '^F', '\x07': '^G', '\x08': '^H', '\x09': '^I', '\x0a': '^J', '\x0b': '^K', '\x0c': '^L', '\x0d': '^M', '\x0e': '^N', '\x0f': '^O', '\x10': '^P', '\x11': '^Q', '\x12': '^R', '\x13': '^S', '\x14': '^T', '\x15': '^U', '\x16': '^V', '\x17': '^W', '\x18': '^X', '\x19': '^Y', '\x1a': '^Z', '\x1b': '^[', # Escape '\x1c': '^\\', '\x1d': '^]', '\x1f': '^_', '\x7f': '^?', # ASCII Delete (backspace). # Special characters. All visualized like Vim does. '\x80': '<80>', '\x81': '<81>', '\x82': '<82>', '\x83': '<83>', '\x84': '<84>', '\x85': '<85>', '\x86': '<86>', '\x87': '<87>', '\x88': '<88>', '\x89': '<89>', '\x8a': '<8a>', '\x8b': '<8b>', '\x8c': '<8c>', '\x8d': '<8d>', '\x8e': '<8e>', '\x8f': '<8f>', '\x90': '<90>', '\x91': '<91>', '\x92': '<92>', '\x93': '<93>', '\x94': '<94>', '\x95': '<95>', '\x96': '<96>', '\x97': '<97>', '\x98': '<98>', '\x99': '<99>', '\x9a': '<9a>', '\x9b': '<9b>', '\x9c': '<9c>', '\x9d': '<9d>', '\x9e': '<9e>', '\x9f': '<9f>', # For the non-breaking space: visualize like Emacs does by default. # (Print a space, but attach the 'nbsp' class that applies the # underline style.) '\xa0': ' ', } def __init__(self, char=' ', style=''): # If this character has to be displayed otherwise, take that one. if char in self.display_mappings: if char == '\xa0': style += ' class:nbsp ' # Will be underlined. else: style += ' class:control-character ' char = self.display_mappings[char] self.char = char self.style = style # Calculate width. (We always need this, so better to store it directly # as a member for performance.) self.width = get_cwidth(char) def __eq__(self, other): return self.char == other.char and self.style == other.style def __ne__(self, other): # Not equal: We don't do `not char.__eq__` here, because of the # performance of calling yet another function. return self.char != other.char or self.style != other.style def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.char, self.style) _CHAR_CACHE = FastDictCache(Char, size=1000 * 1000) Transparent = '[transparent]' class Screen(object): """ Two dimensional buffer of :class:`.Char` instances. """ def __init__(self, default_char=None, initial_width=0, initial_height=0): if default_char is None: default_char = _CHAR_CACHE[' ', Transparent] self.data_buffer = defaultdict(lambda: defaultdict(lambda: default_char)) #: Escape sequences to be injected. self.zero_width_escapes = defaultdict(lambda: defaultdict(lambda: '')) #: Position of the cursor. self.cursor_positions = {} # Map `Window` objects to `Point` objects. #: Visibility of the cursor. self.show_cursor = True #: (Optional) Where to position the menu. E.g. at the start of a completion. #: (We can't use the cursor position, because we don't want the #: completion menu to change its position when we browse through all the #: completions.) self.menu_positions = {} # Map `Window` objects to `Point` objects. #: Currently used width/height of the screen. This will increase when #: data is written to the screen. self.width = initial_width or 0 self.height = initial_height or 0 # Windows that have been drawn. (Each `Window` class will add itself to # this list.) self.visible_windows = [] self._draw_float_functions = [] # List of (z_index, draw_func) def set_cursor_position(self, window, position): " Set the cursor position for a given window. " self.cursor_positions[window] = position def set_menu_position(self, window, position): " Set the cursor position for a given window. " self.menu_positions[window] = position def get_cursor_position(self, window): """ Get the cursor position for a given window. Returns a `Point`. """ try: return self.cursor_positions[window] except KeyError: return Point(x=0, y=0) def get_menu_position(self, window): """ Get the menu position for a given window. (This falls back to the cursor position if no menu position was set.) """ try: return self.menu_positions[window] except KeyError: try: return self.cursor_positions[window] except KeyError: return Point(x=0, y=0) def draw_with_z_index(self, z_index, draw_func): """ Add a draw-function for a `Window` which has a >= 0 z_index. This will be postponed until `draw_all_floats` is called. """ assert isinstance(z_index, int), z_index assert callable(draw_func) self._draw_float_functions.append((z_index, draw_func)) def draw_all_floats(self): """ Draw all float functions in order of z-index. """ # We keep looping because some draw functions could add new functions # to this list. See `FloatContainer`. while self._draw_float_functions: # Sort the floats that we have so far by z_index. functions = sorted(self._draw_float_functions, key=lambda item: item[0]) # Draw only one at a time, then sort everything again. Now floats # might have been added. self._draw_float_functions = functions[1:] functions[0][1]() def append_style_to_content(self, style_str): """ For all the characters in the screen. Set the style string to the given `style_str`. """ b = self.data_buffer char_cache = _CHAR_CACHE append_style = ' ' + style_str for y, row in b.items(): for x, char in row.items(): b[y][x] = char_cache[char.char, char.style + append_style] def fill_area(self, write_position, style='', after=False): """ Fill the content of this area, using the given `style`. The style is prepended before whatever was here before. """ if not style.strip(): return xmin = write_position.xpos xmax = write_position.xpos + write_position.width char_cache = _CHAR_CACHE data_buffer = self.data_buffer if after: append_style = ' ' + style prepend_style = '' else: append_style = '' prepend_style = style + ' ' for y in range(write_position.ypos, write_position.ypos + write_position.height): row = data_buffer[y] for x in range(xmin, xmax): cell = row[x] row[x] = char_cache[cell.char, prepend_style + cell.style + append_style] class WritePosition(object): def __init__(self, xpos, ypos, width, height): assert height >= 0 assert width >= 0 # xpos and ypos can be negative. (A float can be partially visible.) self.xpos = xpos self.ypos = ypos self.width = width self.height = height def __repr__(self): return '%s(x=%r, y=%r, width=%r, height=%r)' % ( self.__class__.__name__, self.xpos, self.ypos, self.width, self.height) prompt_toolkit-2.0.10/prompt_toolkit/layout/utils.py0000644000175100017510000000327413545407204024437 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = [ 'explode_text_fragments', ] class _ExplodedList(list): """ Wrapper around a list, that marks it as 'exploded'. As soon as items are added or the list is extended, the new items are automatically exploded as well. """ def __init__(self, *a, **kw): super(_ExplodedList, self).__init__(*a, **kw) self.exploded = True def append(self, item): self.extend([item]) def extend(self, lst): super(_ExplodedList, self).extend(explode_text_fragments(lst)) def insert(self, index, item): raise NotImplementedError # TODO # TODO: When creating a copy() or [:], return also an _ExplodedList. def __setitem__(self, index, value): """ Ensure that when `(style_str, 'long string')` is set, the string will be exploded. """ if not isinstance(index, slice): index = slice(index, index + 1) value = explode_text_fragments([value]) super(_ExplodedList, self).__setitem__(index, value) def explode_text_fragments(fragments): """ Turn a list of (style_str, text) tuples into another list where each string is exactly one character. It should be fine to call this function several times. Calling this on a list that is already exploded, is a null operation. :param fragments: List of (style, text) tuples. """ # When the fragments is already exploded, don't explode again. if getattr(fragments, 'exploded', False): return fragments result = [] for style, string in fragments: for c in string: result.append((style, c)) return _ExplodedList(result) prompt_toolkit-2.0.10/prompt_toolkit/lexers/0000755000175100017510000000000013545410361022702 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/lexers/__init__.py0000644000175100017510000000063613545407204025022 0ustar jonathanjonathan00000000000000""" Lexer interface and implementations. Used for syntax highlighting. """ from __future__ import unicode_literals from .base import DynamicLexer, Lexer, SimpleLexer from .pygments import PygmentsLexer, RegexSync, SyncFromStart, SyntaxSync __all__ = [ # Base. 'Lexer', 'SimpleLexer', 'DynamicLexer', # Pygments. 'PygmentsLexer', 'RegexSync', 'SyncFromStart', 'SyntaxSync', ] prompt_toolkit-2.0.10/prompt_toolkit/lexers/base.py0000644000175100017510000000405613545407204024175 0ustar jonathanjonathan00000000000000""" Base classes for prompt_toolkit lexers. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import text_type, with_metaclass __all__ = [ 'Lexer', 'SimpleLexer', 'DynamicLexer', ] class Lexer(with_metaclass(ABCMeta, object)): """ Base class for all lexers. """ @abstractmethod def lex_document(self, document): """ Takes a :class:`~prompt_toolkit.document.Document` and returns a callable that takes a line number and returns a list of ``(style_str, text)`` tuples for that line. XXX: Note that in the past, this was supposed to return a list of ``(Token, text)`` tuples, just like a Pygments lexer. """ def invalidation_hash(self): """ When this changes, `lex_document` could give a different output. (Only used for `DynamicLexer`.) """ return id(self) class SimpleLexer(Lexer): """ Lexer that doesn't do any tokenizing and returns the whole input as one token. :param style: The style string for this lexer. """ def __init__(self, style=''): assert isinstance(style, text_type) self.style = style def lex_document(self, document): lines = document.lines def get_line(lineno): " Return the tokens for the given line. " try: return [(self.style, lines[lineno])] except IndexError: return [] return get_line class DynamicLexer(Lexer): """ Lexer class that can dynamically returns any Lexer. :param get_lexer: Callable that returns a :class:`.Lexer` instance. """ def __init__(self, get_lexer): self.get_lexer = get_lexer self._dummy = SimpleLexer() def lex_document(self, document): lexer = self.get_lexer() or self._dummy assert isinstance(lexer, Lexer) return lexer.lex_document(document) def invalidation_hash(self): lexer = self.get_lexer() or self._dummy return id(lexer) prompt_toolkit-2.0.10/prompt_toolkit/lexers/pygments.py0000644000175100017510000002551413545407204025133 0ustar jonathanjonathan00000000000000""" Adaptor classes for using Pygments lexers within prompt_toolkit. This includes syntax synchronization code, so that we don't have to start lexing at the beginning of a document, when displaying a very large text. """ from __future__ import absolute_import, unicode_literals import re from abc import ABCMeta, abstractmethod from six import text_type, with_metaclass from six.moves import range from prompt_toolkit.filters import to_filter from prompt_toolkit.formatted_text.utils import split_lines from prompt_toolkit.styles.pygments import pygments_token_to_classname from .base import Lexer, SimpleLexer __all__ = [ 'PygmentsLexer', 'SyntaxSync', 'SyncFromStart', 'RegexSync', ] class SyntaxSync(with_metaclass(ABCMeta, object)): """ Syntax synchroniser. This is a tool that finds a start position for the lexer. This is especially important when editing big documents; we don't want to start the highlighting by running the lexer from the beginning of the file. That is very slow when editing. """ @abstractmethod def get_sync_start_position(self, document, lineno): """ Return the position from where we can start lexing as a (row, column) tuple. :param document: `Document` instance that contains all the lines. :param lineno: The line that we want to highlight. (We need to return this line, or an earlier position.) """ class SyncFromStart(SyntaxSync): """ Always start the syntax highlighting from the beginning. """ def get_sync_start_position(self, document, lineno): return 0, 0 class RegexSync(SyntaxSync): """ Synchronize by starting at a line that matches the given regex pattern. """ # Never go more than this amount of lines backwards for synchronisation. # That would be too CPU intensive. MAX_BACKWARDS = 500 # Start lexing at the start, if we are in the first 'n' lines and no # synchronisation position was found. FROM_START_IF_NO_SYNC_POS_FOUND = 100 def __init__(self, pattern): assert isinstance(pattern, text_type) self._compiled_pattern = re.compile(pattern) def get_sync_start_position(self, document, lineno): " Scan backwards, and find a possible position to start. " pattern = self._compiled_pattern lines = document.lines # Scan upwards, until we find a point where we can start the syntax # synchronisation. for i in range(lineno, max(-1, lineno - self.MAX_BACKWARDS), -1): match = pattern.match(lines[i]) if match: return i, match.start() # No synchronisation point found. If we aren't that far from the # beginning, start at the very beginning, otherwise, just try to start # at the current line. if lineno < self.FROM_START_IF_NO_SYNC_POS_FOUND: return 0, 0 else: return lineno, 0 @classmethod def from_pygments_lexer_cls(cls, lexer_cls): """ Create a :class:`.RegexSync` instance for this Pygments lexer class. """ patterns = { # For Python, start highlighting at any class/def block. 'Python': r'^\s*(class|def)\s+', 'Python 3': r'^\s*(class|def)\s+', # For HTML, start at any open/close tag definition. 'HTML': r'<[/a-zA-Z]', # For javascript, start at a function. 'JavaScript': r'\bfunction\b' # TODO: Add definitions for other languages. # By default, we start at every possible line. } p = patterns.get(lexer_cls.name, '^') return cls(p) class _TokenCache(dict): """ Cache that converts Pygments tokens into `prompt_toolkit` style objects. ``Token.A.B.C`` will be converted into: ``class:pygments,pygments.A,pygments.A.B,pygments.A.B.C`` """ def __missing__(self, key): result = 'class:' + pygments_token_to_classname(key) self[key] = result return result _token_cache = _TokenCache() class PygmentsLexer(Lexer): """ Lexer that calls a pygments lexer. Example:: from pygments.lexers.html import HtmlLexer lexer = PygmentsLexer(HtmlLexer) Note: Don't forget to also load a Pygments compatible style. E.g.:: from prompt_toolkit.styles.from_pygments import style_from_pygments_cls from pygments.styles import get_style_by_name style = style_from_pygments_cls(get_style_by_name('monokai')) :param pygments_lexer_cls: A `Lexer` from Pygments. :param sync_from_start: Start lexing at the start of the document. This will always give the best results, but it will be slow for bigger documents. (When the last part of the document is display, then the whole document will be lexed by Pygments on every key stroke.) It is recommended to disable this for inputs that are expected to be more than 1,000 lines. :param syntax_sync: `SyntaxSync` object. """ # Minimum amount of lines to go backwards when starting the parser. # This is important when the lines are retrieved in reverse order, or when # scrolling upwards. (Due to the complexity of calculating the vertical # scroll offset in the `Window` class, lines are not always retrieved in # order.) MIN_LINES_BACKWARDS = 50 # When a parser was started this amount of lines back, read the parser # until we get the current line. Otherwise, start a new parser. # (This should probably be bigger than MIN_LINES_BACKWARDS.) REUSE_GENERATOR_MAX_DISTANCE = 100 def __init__(self, pygments_lexer_cls, sync_from_start=True, syntax_sync=None): assert syntax_sync is None or isinstance(syntax_sync, SyntaxSync) self.pygments_lexer_cls = pygments_lexer_cls self.sync_from_start = to_filter(sync_from_start) # Instantiate the Pygments lexer. self.pygments_lexer = pygments_lexer_cls( stripnl=False, stripall=False, ensurenl=False) # Create syntax sync instance. self.syntax_sync = syntax_sync or RegexSync.from_pygments_lexer_cls(pygments_lexer_cls) @classmethod def from_filename(cls, filename, sync_from_start=True): """ Create a `Lexer` from a filename. """ # Inline imports: the Pygments dependency is optional! from pygments.util import ClassNotFound from pygments.lexers import get_lexer_for_filename try: pygments_lexer = get_lexer_for_filename(filename) except ClassNotFound: return SimpleLexer() else: return cls(pygments_lexer.__class__, sync_from_start=sync_from_start) def lex_document(self, document): """ Create a lexer function that takes a line number and returns the list of (style_str, text) tuples as the Pygments lexer returns for that line. """ # Cache of already lexed lines. cache = {} # Pygments generators that are currently lexing. line_generators = {} # Map lexer generator to the line number. def get_syntax_sync(): " The Syntax synchronisation object that we currently use. " if self.sync_from_start(): return SyncFromStart() else: return self.syntax_sync def find_closest_generator(i): " Return a generator close to line 'i', or None if none was found. " for generator, lineno in line_generators.items(): if lineno < i and i - lineno < self.REUSE_GENERATOR_MAX_DISTANCE: return generator def create_line_generator(start_lineno, column=0): """ Create a generator that yields the lexed lines. Each iteration it yields a (line_number, [(token, text), ...]) tuple. """ def get_text_fragments(): text = '\n'.join(document.lines[start_lineno:])[column:] # We call `get_text_fragments_unprocessed`, because `get_tokens` will # still replace \r\n and \r by \n. (We don't want that, # Pygments should return exactly the same amount of text, as we # have given as input.) for _, t, v in self.pygments_lexer.get_tokens_unprocessed(text): # Turn Pygments `Token` object into prompt_toolkit `Token` # objects. yield _token_cache[t], v return enumerate(split_lines(get_text_fragments()), start_lineno) def get_generator(i): """ Find an already started generator that is close, or create a new one. """ # Find closest line generator. generator = find_closest_generator(i) if generator: return generator # No generator found. Determine starting point for the syntax # synchronisation first. # Go at least x lines back. (Make scrolling upwards more # efficient.) i = max(0, i - self.MIN_LINES_BACKWARDS) if i == 0: row = 0 column = 0 else: row, column = get_syntax_sync().get_sync_start_position(document, i) # Find generator close to this point, or otherwise create a new one. generator = find_closest_generator(i) if generator: return generator else: generator = create_line_generator(row, column) # If the column is not 0, ignore the first line. (Which is # incomplete. This happens when the synchronisation algorithm tells # us to start parsing in the middle of a line.) if column: next(generator) row += 1 line_generators[generator] = row return generator def get_line(i): " Return the tokens for a given line number. " try: return cache[i] except KeyError: generator = get_generator(i) # Exhaust the generator, until we find the requested line. for num, line in generator: cache[num] = line if num == i: line_generators[generator] = i # Remove the next item from the cache. # (It could happen that it's already there, because of # another generator that started filling these lines, # but we want to synchronise these lines with the # current lexer's state.) if num + 1 in cache: del cache[num + 1] return cache[num] return [] return get_line prompt_toolkit-2.0.10/prompt_toolkit/log.py0000644000175100017510000000023513545407204022535 0ustar jonathanjonathan00000000000000""" Logging configuration. """ from __future__ import unicode_literals import logging __all__ = [ 'logger', ] logger = logging.getLogger(__package__) prompt_toolkit-2.0.10/prompt_toolkit/mouse_events.py0000644000175100017510000000241613545407204024473 0ustar jonathanjonathan00000000000000""" Mouse events. How it works ------------ The renderer has a 2 dimensional grid of mouse event handlers. (`prompt_toolkit.layout.MouseHandlers`.) When the layout is rendered, the `Window` class will make sure that this grid will also be filled with callbacks. For vt100 terminals, mouse events are received through stdin, just like any other key press. There is a handler among the key bindings that catches these events and forwards them to such a mouse event handler. It passes through the `Window` class where the coordinates are translated from absolute coordinates to coordinates relative to the user control, and there `UIControl.mouse_handler` is called. """ from __future__ import unicode_literals __all__ = [ 'MouseEventType', 'MouseEvent' ] class MouseEventType: MOUSE_UP = 'MOUSE_UP' MOUSE_DOWN = 'MOUSE_DOWN' SCROLL_UP = 'SCROLL_UP' SCROLL_DOWN = 'SCROLL_DOWN' class MouseEvent(object): """ Mouse event, sent to `UIControl.mouse_handler`. :param position: `Point` instance. :param event_type: `MouseEventType`. """ def __init__(self, position, event_type): self.position = position self.event_type = event_type def __repr__(self): return 'MouseEvent(%r, %r)' % (self.position, self.event_type) prompt_toolkit-2.0.10/prompt_toolkit/output/0000755000175100017510000000000013545410361022740 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/output/__init__.py0000644000175100017510000000057313545407204025060 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import DummyOutput, Output from .color_depth import ColorDepth from .defaults import create_output, get_default_output, set_default_output __all__ = [ # Base. 'Output', 'DummyOutput', # Color depth. 'ColorDepth', # Defaults. 'create_output', 'get_default_output', 'set_default_output', ] prompt_toolkit-2.0.10/prompt_toolkit/output/base.py0000644000175100017510000001216513545407204024233 0ustar jonathanjonathan00000000000000""" Interface for an output. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from prompt_toolkit.layout.screen import Size __all__ = [ 'Output', 'DummyOutput', ] class Output(with_metaclass(ABCMeta, object)): """ Base class defining the output interface for a :class:`~prompt_toolkit.renderer.Renderer`. Actual implementations are :class:`~prompt_toolkit.output.vt100.Vt100_Output` and :class:`~prompt_toolkit.output.win32.Win32Output`. """ @abstractmethod def fileno(self): " Return the file descriptor to which we can write for the output. " @abstractmethod def encoding(self): """ Return the encoding for this output, e.g. 'utf-8'. (This is used mainly to know which characters are supported by the output the data, so that the UI can provide alternatives, when required.) """ @abstractmethod def write(self, data): " Write text (Terminal escape sequences will be removed/escaped.) " @abstractmethod def write_raw(self, data): " Write text. " @abstractmethod def set_title(self, title): " Set terminal title. " @abstractmethod def clear_title(self): " Clear title again. (or restore previous title.) " @abstractmethod def flush(self): " Write to output stream and flush. " @abstractmethod def erase_screen(self): """ Erases the screen with the background colour and moves the cursor to home. """ @abstractmethod def enter_alternate_screen(self): " Go to the alternate screen buffer. (For full screen applications). " @abstractmethod def quit_alternate_screen(self): " Leave the alternate screen buffer. " @abstractmethod def enable_mouse_support(self): " Enable mouse. " @abstractmethod def disable_mouse_support(self): " Disable mouse. " @abstractmethod def erase_end_of_line(self): """ Erases from the current cursor position to the end of the current line. """ @abstractmethod def erase_down(self): """ Erases the screen from the current line down to the bottom of the screen. """ @abstractmethod def reset_attributes(self): " Reset color and styling attributes. " @abstractmethod def set_attributes(self, attrs, color_depth): " Set new color and styling attributes. " @abstractmethod def disable_autowrap(self): " Disable auto line wrapping. " @abstractmethod def enable_autowrap(self): " Enable auto line wrapping. " @abstractmethod def cursor_goto(self, row=0, column=0): " Move cursor position. " @abstractmethod def cursor_up(self, amount): " Move cursor `amount` place up. " @abstractmethod def cursor_down(self, amount): " Move cursor `amount` place down. " @abstractmethod def cursor_forward(self, amount): " Move cursor `amount` place forward. " @abstractmethod def cursor_backward(self, amount): " Move cursor `amount` place backward. " @abstractmethod def hide_cursor(self): " Hide cursor. " @abstractmethod def show_cursor(self): " Show cursor. " def ask_for_cpr(self): """ Asks for a cursor position report (CPR). (VT100 only.) """ def bell(self): " Sound bell. " def enable_bracketed_paste(self): " For vt100 only. " def disable_bracketed_paste(self): " For vt100 only. " def scroll_buffer_to_prompt(self): " For Win32 only. " class DummyOutput(Output): """ For testing. An output class that doesn't render anything. """ def fileno(self): " There is no sensible default for fileno(). " raise NotImplementedError def encoding(self): return 'utf-8' def write(self, data): pass def write_raw(self, data): pass def set_title(self, title): pass def clear_title(self): pass def flush(self): pass def erase_screen(self): pass def enter_alternate_screen(self): pass def quit_alternate_screen(self): pass def enable_mouse_support(self): pass def disable_mouse_support(self): pass def erase_end_of_line(self): pass def erase_down(self): pass def reset_attributes(self): pass def set_attributes(self, attrs, color_depth): pass def disable_autowrap(self): pass def enable_autowrap(self): pass def cursor_goto(self, row=0, column=0): pass def cursor_up(self, amount): pass def cursor_down(self, amount): pass def cursor_forward(self, amount): pass def cursor_backward(self, amount): pass def hide_cursor(self): pass def show_cursor(self): pass def ask_for_cpr(self): pass def bell(self): pass def enable_bracketed_paste(self): pass def disable_bracketed_paste(self): pass def scroll_buffer_to_prompt(self): pass def get_size(self): return Size(rows=40, columns=80) def get_rows_below_cursor_position(self): return 40 prompt_toolkit-2.0.10/prompt_toolkit/output/color_depth.py0000644000175100017510000000273313545407204025623 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import os from prompt_toolkit.utils import is_windows __all__ = [ 'ColorDepth', ] class ColorDepth(object): """ Possible color depth values for the output. """ #: One color only. DEPTH_1_BIT = 'DEPTH_1_BIT' #: ANSI Colors. DEPTH_4_BIT = 'DEPTH_4_BIT' #: The default. DEPTH_8_BIT = 'DEPTH_8_BIT' #: 24 bit True color. DEPTH_24_BIT = 'DEPTH_24_BIT' # Aliases. MONOCHROME = DEPTH_1_BIT ANSI_COLORS_ONLY = DEPTH_4_BIT DEFAULT = DEPTH_8_BIT TRUE_COLOR = DEPTH_24_BIT _ALL = [DEPTH_1_BIT, DEPTH_4_BIT, DEPTH_8_BIT, DEPTH_24_BIT] @classmethod def default(cls, term=''): """ If the user doesn't specify a color depth, use this as a default. """ if term in ('linux', 'eterm-color'): return cls.DEPTH_4_BIT # For now, always use 4 bit color on Windows 10 by default, even when # vt100 escape sequences with ENABLE_VIRTUAL_TERMINAL_PROCESSING are # supported. We don't have a reliable way yet to know whether our # console supports true color or only 4-bit. if is_windows() and 'PROMPT_TOOLKIT_COLOR_DEPTH' not in os.environ: return cls.DEPTH_4_BIT # Check the `PROMPT_TOOLKIT_COLOR_DEPTH` environment variable. if os.environ.get('PROMPT_TOOLKIT_COLOR_DEPTH') in cls._ALL: return os.environ['PROMPT_TOOLKIT_COLOR_DEPTH'] return cls.DEPTH_8_BIT prompt_toolkit-2.0.10/prompt_toolkit/output/conemu.py0000644000175100017510000000254413545407204024607 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.renderer import Output from .vt100 import Vt100_Output from .win32 import Win32Output __all__ = [ 'ConEmuOutput', ] class ConEmuOutput(object): """ ConEmu (Windows) output abstraction. ConEmu is a Windows console application, but it also supports ANSI escape sequences. This output class is actually a proxy to both `Win32Output` and `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but all cursor movements and scrolling happens through the `Vt100_Output`. This way, we can have 256 colors in ConEmu and Cmder. Rendering will be even a little faster as well. http://conemu.github.io/ http://gooseberrycreative.com/cmder/ """ def __init__(self, stdout): self.win32_output = Win32Output(stdout) self.vt100_output = Vt100_Output(stdout, lambda: None) def __getattr__(self, name): if name in ('get_size', 'get_rows_below_cursor_position', 'enable_mouse_support', 'disable_mouse_support', 'scroll_buffer_to_prompt', 'get_win32_screen_buffer_info', 'enable_bracketed_paste', 'disable_bracketed_paste'): return getattr(self.win32_output, name) else: return getattr(self.vt100_output, name) Output.register(ConEmuOutput) prompt_toolkit-2.0.10/prompt_toolkit/output/defaults.py0000644000175100017510000000373313545407204025131 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import sys from prompt_toolkit.application.current import get_app from prompt_toolkit.eventloop.context import TaskLocal, TaskLocalNotSetError from prompt_toolkit.utils import ( get_term_environment_variable, is_conemu_ansi, is_windows, ) from .base import Output __all__ = [ 'create_output', 'get_default_output', 'set_default_output', ] def create_output(stdout=None): """ Return an :class:`~prompt_toolkit.output.Output` instance for the command line. :param stdout: The stdout object """ stdout = stdout or sys.__stdout__ if is_windows(): from .conemu import ConEmuOutput from .win32 import Win32Output from .windows10 import is_win_vt100_enabled, Windows10_Output if is_win_vt100_enabled(): return Windows10_Output(stdout) if is_conemu_ansi(): return ConEmuOutput(stdout) else: return Win32Output(stdout) else: from .vt100 import Vt100_Output return Vt100_Output.from_pty( stdout, term=get_term_environment_variable()) _default_output = TaskLocal() def get_default_output(): """ Get the output class to be used by default. Called when creating a new Application(), when no `Output` has been passed. """ try: value = _default_output.get() except TaskLocalNotSetError: # If an application is already running, take the output from there. # (This is important for the "ENTER for continue" prompts after # executing system commands and displaying readline-style completions.) app = get_app(return_none=True) if app: return app.output return create_output() else: return value def set_default_output(output): """ Set the default `Output` class. (Used for instance, for the telnet submodule.) """ assert isinstance(output, Output) _default_output.set(output) prompt_toolkit-2.0.10/prompt_toolkit/output/vt100.py0000644000175100017510000004700713545407204024176 0ustar jonathanjonathan00000000000000""" Output for vt100 terminals. A lot of thanks, regarding outputting of colors, goes to the Pygments project: (We don't rely on Pygments anymore, because many things are very custom, and everything has been highly optimized.) http://pygments.org/ """ from __future__ import unicode_literals import array import errno import sys import six from six.moves import range from prompt_toolkit.layout.screen import Size from prompt_toolkit.output import Output from prompt_toolkit.styles.base import ANSI_COLOR_NAMES from .color_depth import ColorDepth __all__ = [ 'Vt100_Output', ] FG_ANSI_COLORS = { 'ansidefault': 39, # Low intensity. 'ansiblack': 30, 'ansired': 31, 'ansigreen': 32, 'ansiyellow': 33, 'ansiblue': 34, 'ansimagenta': 35, 'ansicyan': 36, 'ansigray': 37, # High intensity. 'ansibrightblack': 90, 'ansibrightred': 91, 'ansibrightgreen': 92, 'ansibrightyellow': 93, 'ansibrightblue': 94, 'ansibrightmagenta': 95, 'ansibrightcyan': 96, 'ansiwhite': 97, } BG_ANSI_COLORS = { 'ansidefault': 49, # Low intensity. 'ansiblack': 40, 'ansired': 41, 'ansigreen': 42, 'ansiyellow': 43, 'ansiblue': 44, 'ansimagenta': 45, 'ansicyan': 46, 'ansigray': 47, # High intensity. 'ansibrightblack': 100, 'ansibrightred': 101, 'ansibrightgreen': 102, 'ansibrightyellow': 103, 'ansibrightblue': 104, 'ansibrightmagenta': 105, 'ansibrightcyan': 106, 'ansiwhite': 107, } ANSI_COLORS_TO_RGB = { 'ansidefault': (0x00, 0x00, 0x00), # Don't use, 'default' doesn't really have a value. 'ansiblack': (0x00, 0x00, 0x00), 'ansigray': (0xe5, 0xe5, 0xe5), 'ansibrightblack': (0x7f, 0x7f, 0x7f), 'ansiwhite': (0xff, 0xff, 0xff), # Low intensity. 'ansired': (0xcd, 0x00, 0x00), 'ansigreen': (0x00, 0xcd, 0x00), 'ansiyellow': (0xcd, 0xcd, 0x00), 'ansiblue': (0x00, 0x00, 0xcd), 'ansimagenta': (0xcd, 0x00, 0xcd), 'ansicyan': (0x00, 0xcd, 0xcd), # High intensity. 'ansibrightred': (0xff, 0x00, 0x00), 'ansibrightgreen': (0x00, 0xff, 0x00), 'ansibrightyellow': (0xff, 0xff, 0x00), 'ansibrightblue': (0x00, 0x00, 0xff), 'ansibrightmagenta': (0xff, 0x00, 0xff), 'ansibrightcyan': (0x00, 0xff, 0xff), } assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) def _get_closest_ansi_color(r, g, b, exclude=()): """ Find closest ANSI color. Return it by name. :param r: Red (Between 0 and 255.) :param g: Green (Between 0 and 255.) :param b: Blue (Between 0 and 255.) :param exclude: A tuple of color names to exclude. (E.g. ``('ansired', )``.) """ assert isinstance(exclude, tuple) # When we have a bit of saturation, avoid the gray-like colors, otherwise, # too often the distance to the gray color is less. saturation = abs(r - g) + abs(g - b) + abs(b - r) # Between 0..510 if saturation > 30: exclude += ('ansilightgray', 'ansidarkgray', 'ansiwhite', 'ansiblack') # Take the closest color. # (Thanks to Pygments for this part.) distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) match = 'ansidefault' for name, (r2, g2, b2) in ANSI_COLORS_TO_RGB.items(): if name != 'ansidefault' and name not in exclude: d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 if d < distance: match = name distance = d return match class _16ColorCache(dict): """ Cache which maps (r, g, b) tuples to 16 ansi colors. :param bg: Cache for background colors, instead of foreground. """ def __init__(self, bg=False): assert isinstance(bg, bool) self.bg = bg def get_code(self, value, exclude=()): """ Return a (ansi_code, ansi_name) tuple. (E.g. ``(44, 'ansiblue')``.) for a given (r,g,b) value. """ key = (value, exclude) if key not in self: self[key] = self._get(value, exclude) return self[key] def _get(self, value, exclude=()): r, g, b = value match = _get_closest_ansi_color(r, g, b, exclude=exclude) # Turn color name into code. if self.bg: code = BG_ANSI_COLORS[match] else: code = FG_ANSI_COLORS[match] self[value] = code return code, match class _256ColorCache(dict): """ Cache which maps (r, g, b) tuples to 256 colors. """ def __init__(self): # Build color table. colors = [] # colors 0..15: 16 basic colors colors.append((0x00, 0x00, 0x00)) # 0 colors.append((0xcd, 0x00, 0x00)) # 1 colors.append((0x00, 0xcd, 0x00)) # 2 colors.append((0xcd, 0xcd, 0x00)) # 3 colors.append((0x00, 0x00, 0xee)) # 4 colors.append((0xcd, 0x00, 0xcd)) # 5 colors.append((0x00, 0xcd, 0xcd)) # 6 colors.append((0xe5, 0xe5, 0xe5)) # 7 colors.append((0x7f, 0x7f, 0x7f)) # 8 colors.append((0xff, 0x00, 0x00)) # 9 colors.append((0x00, 0xff, 0x00)) # 10 colors.append((0xff, 0xff, 0x00)) # 11 colors.append((0x5c, 0x5c, 0xff)) # 12 colors.append((0xff, 0x00, 0xff)) # 13 colors.append((0x00, 0xff, 0xff)) # 14 colors.append((0xff, 0xff, 0xff)) # 15 # colors 16..232: the 6x6x6 color cube valuerange = (0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff) for i in range(217): r = valuerange[(i // 36) % 6] g = valuerange[(i // 6) % 6] b = valuerange[i % 6] colors.append((r, g, b)) # colors 233..253: grayscale for i in range(1, 22): v = 8 + i * 10 colors.append((v, v, v)) self.colors = colors def __missing__(self, value): r, g, b = value # Find closest color. # (Thanks to Pygments for this!) distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) match = 0 for i, (r2, g2, b2) in enumerate(self.colors): if i >= 16: # XXX: We ignore the 16 ANSI colors when mapping RGB # to the 256 colors, because these highly depend on # the color scheme of the terminal. d = (r - r2) ** 2 + (g - g2) ** 2 + (b - b2) ** 2 if d < distance: match = i distance = d # Turn color name into code. self[value] = match return match _16_fg_colors = _16ColorCache(bg=False) _16_bg_colors = _16ColorCache(bg=True) _256_colors = _256ColorCache() class _EscapeCodeCache(dict): """ Cache for VT100 escape codes. It maps (fgcolor, bgcolor, bold, underline, reverse) tuples to VT100 escape sequences. :param true_color: When True, use 24bit colors instead of 256 colors. """ def __init__(self, color_depth): assert color_depth in ColorDepth._ALL self.color_depth = color_depth def __missing__(self, attrs): fgcolor, bgcolor, bold, underline, italic, blink, reverse, hidden = attrs parts = [] parts.extend(self._colors_to_code(fgcolor, bgcolor)) if bold: parts.append('1') if italic: parts.append('3') if blink: parts.append('5') if underline: parts.append('4') if reverse: parts.append('7') if hidden: parts.append('8') if parts: result = '\x1b[0;' + ';'.join(parts) + 'm' else: result = '\x1b[0m' self[attrs] = result return result def _color_name_to_rgb(self, color): " Turn 'ffffff', into (0xff, 0xff, 0xff). " try: rgb = int(color, 16) except ValueError: raise else: r = (rgb >> 16) & 0xff g = (rgb >> 8) & 0xff b = rgb & 0xff return r, g, b def _colors_to_code(self, fg_color, bg_color): " Return a tuple with the vt100 values that represent this color. " # When requesting ANSI colors only, and both fg/bg color were converted # to ANSI, ensure that the foreground and background color are not the # same. (Unless they were explicitly defined to be the same color.) fg_ansi = [()] def get(color, bg): table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS if not color or self.color_depth == ColorDepth.DEPTH_1_BIT: return () # 16 ANSI colors. (Given by name.) elif color in table: return (table[color], ) # RGB colors. (Defined as 'ffffff'.) else: try: rgb = self._color_name_to_rgb(color) except ValueError: return () # When only 16 colors are supported, use that. if self.color_depth == ColorDepth.DEPTH_4_BIT: if bg: # Background. if fg_color != bg_color: exclude = (fg_ansi[0], ) else: exclude = () code, name = _16_bg_colors.get_code(rgb, exclude=exclude) return (code, ) else: # Foreground. code, name = _16_fg_colors.get_code(rgb) fg_ansi[0] = name return (code, ) # True colors. (Only when this feature is enabled.) elif self.color_depth == ColorDepth.DEPTH_24_BIT: r, g, b = rgb return (48 if bg else 38, 2, r, g, b) # 256 RGB colors. else: return (48 if bg else 38, 5, _256_colors[rgb]) result = [] result.extend(get(fg_color, False)) result.extend(get(bg_color, True)) return map(six.text_type, result) def _get_size(fileno): # Thanks to fabric (fabfile.org), and # http://sqizit.bartletts.id.au/2011/02/14/pseudo-terminals-in-python/ """ Get the size of this pseudo terminal. :param fileno: stdout.fileno() :returns: A (rows, cols) tuple. """ # Inline imports, because these modules are not available on Windows. # (This file is used by ConEmuOutput, which is used on Windows.) import fcntl import termios # Buffer for the C call buf = array.array(b'h' if six.PY2 else u'h', [0, 0, 0, 0]) # Do TIOCGWINSZ (Get) # Note: We should not pass 'True' as a fourth parameter to 'ioctl'. (True # is the default.) This causes segmentation faults on some systems. # See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/364 fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf) # Return rows, cols return buf[0], buf[1] class Vt100_Output(Output): """ :param get_size: A callable which returns the `Size` of the output terminal. :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property. :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...) :param write_binary: Encode the output before writing it. If `True` (the default), the `stdout` object is supposed to expose an `encoding` attribute. """ _fds_not_a_terminal = set() # For the error messages. Only display "Output # is not a terminal" once per file descriptor. def __init__(self, stdout, get_size, term=None, write_binary=True): assert callable(get_size) assert term is None or isinstance(term, six.text_type) assert all(hasattr(stdout, a) for a in ('write', 'flush')) if write_binary: assert hasattr(stdout, 'encoding') self._buffer = [] self.stdout = stdout self.write_binary = write_binary self.get_size = get_size self.term = term or 'xterm' # Cache for escape codes. self._escape_code_caches = { ColorDepth.DEPTH_1_BIT: _EscapeCodeCache(ColorDepth.DEPTH_1_BIT), ColorDepth.DEPTH_4_BIT: _EscapeCodeCache(ColorDepth.DEPTH_4_BIT), ColorDepth.DEPTH_8_BIT: _EscapeCodeCache(ColorDepth.DEPTH_8_BIT), ColorDepth.DEPTH_24_BIT: _EscapeCodeCache(ColorDepth.DEPTH_24_BIT), } @classmethod def from_pty(cls, stdout, term=None): """ Create an Output class from a pseudo terminal. (This will take the dimensions by reading the pseudo terminal attributes.) """ # Normally, this requires a real TTY device, but people instantiate # this class often during unit tests as well. For convenience, we print # an error message, use standard dimensions, and go on. isatty = stdout.isatty() fd = stdout.fileno() if not isatty and fd not in cls._fds_not_a_terminal: msg = 'Warning: Output is not to a terminal (fd=%r).\n' sys.stderr.write(msg % fd) cls._fds_not_a_terminal.add(fd) def get_size(): # If terminal (incorrectly) reports its size as 0, pick a # reasonable default. See # https://github.com/ipython/ipython/issues/10071 rows, columns = (None, None) if isatty: rows, columns = _get_size(stdout.fileno()) return Size(rows=rows or 24, columns=columns or 80) return cls(stdout, get_size, term=term) def fileno(self): " Return file descriptor. " return self.stdout.fileno() def encoding(self): " Return encoding used for stdout. " return self.stdout.encoding def write_raw(self, data): """ Write raw data to output. """ self._buffer.append(data) def write(self, data): """ Write text to output. (Removes vt100 escape codes. -- used for safely writing text.) """ self._buffer.append(data.replace('\x1b', '?')) def set_title(self, title): """ Set terminal title. """ if self.term not in ('linux', 'eterm-color'): # Not supported by the Linux console. self.write_raw('\x1b]2;%s\x07' % title.replace('\x1b', '').replace('\x07', '')) def clear_title(self): self.set_title('') def erase_screen(self): """ Erases the screen with the background colour and moves the cursor to home. """ self.write_raw('\x1b[2J') def enter_alternate_screen(self): self.write_raw('\x1b[?1049h\x1b[H') def quit_alternate_screen(self): self.write_raw('\x1b[?1049l') def enable_mouse_support(self): self.write_raw('\x1b[?1000h') # Enable urxvt Mouse mode. (For terminals that understand this.) self.write_raw('\x1b[?1015h') # Also enable Xterm SGR mouse mode. (For terminals that understand this.) self.write_raw('\x1b[?1006h') # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr # extensions. def disable_mouse_support(self): self.write_raw('\x1b[?1000l') self.write_raw('\x1b[?1015l') self.write_raw('\x1b[?1006l') def erase_end_of_line(self): """ Erases from the current cursor position to the end of the current line. """ self.write_raw('\x1b[K') def erase_down(self): """ Erases the screen from the current line down to the bottom of the screen. """ self.write_raw('\x1b[J') def reset_attributes(self): self.write_raw('\x1b[0m') def set_attributes(self, attrs, color_depth): """ Create new style and output. :param attrs: `Attrs` instance. """ # Get current depth. escape_code_cache = self._escape_code_caches[color_depth] # Write escape character. self.write_raw(escape_code_cache[attrs]) def disable_autowrap(self): self.write_raw('\x1b[?7l') def enable_autowrap(self): self.write_raw('\x1b[?7h') def enable_bracketed_paste(self): self.write_raw('\x1b[?2004h') def disable_bracketed_paste(self): self.write_raw('\x1b[?2004l') def cursor_goto(self, row=0, column=0): """ Move cursor position. """ self.write_raw('\x1b[%i;%iH' % (row, column)) def cursor_up(self, amount): if amount == 0: pass elif amount == 1: self.write_raw('\x1b[A') else: self.write_raw('\x1b[%iA' % amount) def cursor_down(self, amount): if amount == 0: pass elif amount == 1: # Note: Not the same as '\n', '\n' can cause the window content to # scroll. self.write_raw('\x1b[B') else: self.write_raw('\x1b[%iB' % amount) def cursor_forward(self, amount): if amount == 0: pass elif amount == 1: self.write_raw('\x1b[C') else: self.write_raw('\x1b[%iC' % amount) def cursor_backward(self, amount): if amount == 0: pass elif amount == 1: self.write_raw('\b') # '\x1b[D' else: self.write_raw('\x1b[%iD' % amount) def hide_cursor(self): self.write_raw('\x1b[?25l') def show_cursor(self): self.write_raw('\x1b[?12l\x1b[?25h') # Stop blinking cursor and show. def flush(self): """ Write to output stream and flush. """ if not self._buffer: return data = ''.join(self._buffer) try: # (We try to encode ourself, because that way we can replace # characters that don't exist in the character set, avoiding # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.) # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968' # for sys.stdout.encoding in xterm. if self.write_binary: if hasattr(self.stdout, 'buffer'): out = self.stdout.buffer # Py3. else: out = self.stdout out.write(data.encode(self.stdout.encoding or 'utf-8', 'replace')) else: self.stdout.write(data) self.stdout.flush() except IOError as e: if e.args and e.args[0] == errno.EINTR: # Interrupted system call. Can happen in case of a window # resize signal. (Just ignore. The resize handler will render # again anyway.) pass elif e.args and e.args[0] == 0: # This can happen when there is a lot of output and the user # sends a KeyboardInterrupt by pressing Control-C. E.g. in # a Python REPL when we execute "while True: print('test')". # (The `ptpython` REPL uses this `Output` class instead of # `stdout` directly -- in order to be network transparent.) # So, just ignore. pass else: raise self._buffer = [] def ask_for_cpr(self): """ Asks for a cursor position report (CPR). """ self.write_raw('\x1b[6n') self.flush() def bell(self): " Sound bell. " self.write_raw('\a') self.flush() prompt_toolkit-2.0.10/prompt_toolkit/output/win32.py0000644000175100017510000004707513545407204024273 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import os from ctypes import ( ArgumentError, byref, c_char, c_long, c_uint, c_ulong, pointer, windll, ) from ctypes.wintypes import DWORD, HANDLE import six from prompt_toolkit.renderer import Output from prompt_toolkit.styles import ANSI_COLOR_NAMES from prompt_toolkit.utils import get_cwidth from prompt_toolkit.win32_types import ( CONSOLE_SCREEN_BUFFER_INFO, COORD, SMALL_RECT, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, ) from .color_depth import ColorDepth __all__ = [ 'Win32Output', ] def _coord_byval(coord): """ Turns a COORD object into a c_long. This will cause it to be passed by value instead of by reference. (That is what I think at least.) When running ``ptipython`` is run (only with IPython), we often got the following error:: Error in 'SetConsoleCursorPosition'. ArgumentError("argument 2: : wrong type",) argument 2: : wrong type It was solved by turning ``COORD`` parameters into a ``c_long`` like this. More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx """ return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF) #: If True: write the output of the renderer also to the following file. This #: is very useful for debugging. (e.g.: to see that we don't write more bytes #: than required.) _DEBUG_RENDER_OUTPUT = False _DEBUG_RENDER_OUTPUT_FILENAME = r'prompt-toolkit-windows-output.log' class NoConsoleScreenBufferError(Exception): """ Raised when the application is not running inside a Windows Console, but the user tries to instantiate Win32Output. """ def __init__(self): # Are we running in 'xterm' on Windows, like git-bash for instance? xterm = 'xterm' in os.environ.get('TERM', '') if xterm: message = ('Found %s, while expecting a Windows console. ' 'Maybe try to run this program using "winpty" ' 'or run it in cmd.exe instead. Or otherwise, ' 'in case of Cygwin, use the Python executable ' 'that is compiled for Cygwin.' % os.environ['TERM']) else: message = 'No Windows console found. Are you running cmd.exe?' super(NoConsoleScreenBufferError, self).__init__(message) class Win32Output(Output): """ I/O abstraction for rendering to Windows consoles. (cmd.exe and similar.) """ def __init__(self, stdout, use_complete_width=False): self.use_complete_width = use_complete_width self._buffer = [] self.stdout = stdout self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) self._in_alternate_screen = False self._hidden = False self.color_lookup_table = ColorLookupTable() # Remember the default console colors. info = self.get_win32_screen_buffer_info() self.default_attrs = info.wAttributes if info else 15 if _DEBUG_RENDER_OUTPUT: self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, 'ab') def fileno(self): " Return file descriptor. " return self.stdout.fileno() def encoding(self): " Return encoding used for stdout. " return self.stdout.encoding def write(self, data): if self._hidden: data = ' ' * get_cwidth(data) self._buffer.append(data) def write_raw(self, data): " For win32, there is no difference between write and write_raw. " self.write(data) def get_size(self): from prompt_toolkit.layout.screen import Size info = self.get_win32_screen_buffer_info() # We take the width of the *visible* region as the size. Not the width # of the complete screen buffer. (Unless use_complete_width has been # set.) if self.use_complete_width: width = info.dwSize.X else: width = info.srWindow.Right - info.srWindow.Left height = info.srWindow.Bottom - info.srWindow.Top + 1 # We avoid the right margin, windows will wrap otherwise. maxwidth = info.dwSize.X - 1 width = min(maxwidth, width) # Create `Size` object. return Size(rows=height, columns=width) def _winapi(self, func, *a, **kw): """ Flush and call win API function. """ self.flush() if _DEBUG_RENDER_OUTPUT: self.LOG.write(('%r' % func.__name__).encode('utf-8') + b'\n') self.LOG.write(b' ' + ', '.join(['%r' % i for i in a]).encode('utf-8') + b'\n') self.LOG.write(b' ' + ', '.join(['%r' % type(i) for i in a]).encode('utf-8') + b'\n') self.LOG.flush() try: return func(*a, **kw) except ArgumentError as e: if _DEBUG_RENDER_OUTPUT: self.LOG.write((' Error in %r %r %s\n' % (func.__name__, e, e)).encode('utf-8')) def get_win32_screen_buffer_info(self): """ Return Screen buffer info. """ # NOTE: We don't call the `GetConsoleScreenBufferInfo` API through # `self._winapi`. Doing so causes Python to crash on certain 64bit # Python versions. (Reproduced with 64bit Python 2.7.6, on Windows # 10). It is not clear why. Possibly, it has to do with passing # these objects as an argument, or through *args. # The Python documentation contains the following - possibly related - warning: # ctypes does not support passing unions or structures with # bit-fields to functions by value. While this may work on 32-bit # x86, it's not guaranteed by the library to work in the general # case. Unions and structures with bit-fields should always be # passed to functions by pointer. # Also see: # - https://github.com/ipython/ipython/issues/10070 # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/406 # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86 self.flush() sbinfo = CONSOLE_SCREEN_BUFFER_INFO() success = windll.kernel32.GetConsoleScreenBufferInfo(self.hconsole, byref(sbinfo)) # success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo, # self.hconsole, byref(sbinfo)) if success: return sbinfo else: raise NoConsoleScreenBufferError def set_title(self, title): """ Set terminal title. """ assert isinstance(title, six.text_type) self._winapi(windll.kernel32.SetConsoleTitleW, title) def clear_title(self): self._winapi(windll.kernel32.SetConsoleTitleW, '') def erase_screen(self): start = COORD(0, 0) sbinfo = self.get_win32_screen_buffer_info() length = sbinfo.dwSize.X * sbinfo.dwSize.Y self.cursor_goto(row=0, column=0) self._erase(start, length) def erase_down(self): sbinfo = self.get_win32_screen_buffer_info() size = sbinfo.dwSize start = sbinfo.dwCursorPosition length = ((size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y)) self._erase(start, length) def erase_end_of_line(self): """ """ sbinfo = self.get_win32_screen_buffer_info() start = sbinfo.dwCursorPosition length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X self._erase(start, length) def _erase(self, start, length): chars_written = c_ulong() self._winapi(windll.kernel32.FillConsoleOutputCharacterA, self.hconsole, c_char(b' '), DWORD(length), _coord_byval(start), byref(chars_written)) # Reset attributes. sbinfo = self.get_win32_screen_buffer_info() self._winapi(windll.kernel32.FillConsoleOutputAttribute, self.hconsole, sbinfo.wAttributes, length, _coord_byval(start), byref(chars_written)) def reset_attributes(self): " Reset the console foreground/background color. " self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, self.default_attrs) self._hidden = False def set_attributes(self, attrs, color_depth): fgcolor, bgcolor, bold, underline, italic, blink, reverse, self._hidden = attrs # Start from the default attributes. attrs = self.default_attrs if color_depth != ColorDepth.DEPTH_1_BIT: # Override the last four bits: foreground color. if fgcolor: attrs = attrs & ~0xf attrs |= self.color_lookup_table.lookup_fg_color(fgcolor) # Override the next four bits: background color. if bgcolor: attrs = attrs & ~0xf0 attrs |= self.color_lookup_table.lookup_bg_color(bgcolor) # Reverse: swap these four bits groups. if reverse: attrs = (attrs & ~0xff) | ((attrs & 0xf) << 4) | ((attrs & 0xf0) >> 4) self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, attrs) def disable_autowrap(self): # Not supported by Windows. pass def enable_autowrap(self): # Not supported by Windows. pass def cursor_goto(self, row=0, column=0): pos = COORD(x=column, y=row) self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) def cursor_up(self, amount): sr = self.get_win32_screen_buffer_info().dwCursorPosition pos = COORD(sr.X, sr.Y - amount) self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) def cursor_down(self, amount): self.cursor_up(-amount) def cursor_forward(self, amount): sr = self.get_win32_screen_buffer_info().dwCursorPosition # assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount) pos = COORD(max(0, sr.X + amount), sr.Y) self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) def cursor_backward(self, amount): self.cursor_forward(-amount) def flush(self): """ Write to output stream and flush. """ if not self._buffer: # Only flush stdout buffer. (It could be that Python still has # something in its buffer. -- We want to be sure to print that in # the correct color.) self.stdout.flush() return data = ''.join(self._buffer) if _DEBUG_RENDER_OUTPUT: self.LOG.write(('%r' % data).encode('utf-8') + b'\n') self.LOG.flush() # Print characters one by one. This appears to be the best solution # in oder to avoid traces of vertical lines when the completion # menu disappears. for b in data: written = DWORD() retval = windll.kernel32.WriteConsoleW(self.hconsole, b, 1, byref(written), None) assert retval != 0 self._buffer = [] def get_rows_below_cursor_position(self): info = self.get_win32_screen_buffer_info() return info.srWindow.Bottom - info.dwCursorPosition.Y + 1 def scroll_buffer_to_prompt(self): """ To be called before drawing the prompt. This should scroll the console to left, with the cursor at the bottom (if possible). """ # Get current window size info = self.get_win32_screen_buffer_info() sr = info.srWindow cursor_pos = info.dwCursorPosition result = SMALL_RECT() # Scroll to the left. result.Left = 0 result.Right = sr.Right - sr.Left # Scroll vertical win_height = sr.Bottom - sr.Top if 0 < sr.Bottom - cursor_pos.Y < win_height - 1: # no vertical scroll if cursor already on the screen result.Bottom = sr.Bottom else: result.Bottom = max(win_height, cursor_pos.Y) result.Top = result.Bottom - win_height # Scroll API self._winapi(windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result)) def enter_alternate_screen(self): """ Go to alternate screen buffer. """ if not self._in_alternate_screen: GENERIC_READ = 0x80000000 GENERIC_WRITE = 0x40000000 # Create a new console buffer and activate that one. handle = HANDLE(self._winapi(windll.kernel32.CreateConsoleScreenBuffer, GENERIC_READ|GENERIC_WRITE, DWORD(0), None, DWORD(1), None)) self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle) self.hconsole = handle self._in_alternate_screen = True def quit_alternate_screen(self): """ Make stdout again the active buffer. """ if self._in_alternate_screen: stdout = HANDLE(self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE)) self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout) self._winapi(windll.kernel32.CloseHandle, self.hconsole) self.hconsole = stdout self._in_alternate_screen = False def enable_mouse_support(self): ENABLE_MOUSE_INPUT = 0x10 handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) original_mode = DWORD() self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value | ENABLE_MOUSE_INPUT) def disable_mouse_support(self): ENABLE_MOUSE_INPUT = 0x10 handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) original_mode = DWORD() self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value & ~ ENABLE_MOUSE_INPUT) def hide_cursor(self): pass def show_cursor(self): pass @classmethod def win32_refresh_window(cls): """ Call win32 API to refresh the whole Window. This is sometimes necessary when the application paints background for completion menus. When the menu disappears, it leaves traces due to a bug in the Windows Console. Sending a repaint request solves it. """ # Get console handle handle = HANDLE(windll.kernel32.GetConsoleWindow()) RDW_INVALIDATE = 0x0001 windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE)) class FOREGROUND_COLOR: BLACK = 0x0000 BLUE = 0x0001 GREEN = 0x0002 CYAN = 0x0003 RED = 0x0004 MAGENTA = 0x0005 YELLOW = 0x0006 GRAY = 0x0007 INTENSITY = 0x0008 # Foreground color is intensified. class BACKGROUND_COLOR: BLACK = 0x0000 BLUE = 0x0010 GREEN = 0x0020 CYAN = 0x0030 RED = 0x0040 MAGENTA = 0x0050 YELLOW = 0x0060 GRAY = 0x0070 INTENSITY = 0x0080 # Background color is intensified. def _create_ansi_color_dict(color_cls): " Create a table that maps the 16 named ansi colors to their Windows code. " return { 'ansidefault': color_cls.BLACK, 'ansiblack': color_cls.BLACK, 'ansigray': color_cls.GRAY, 'ansibrightblack': color_cls.BLACK | color_cls.INTENSITY, 'ansiwhite': color_cls.GRAY | color_cls.INTENSITY, # Low intensity. 'ansired': color_cls.RED, 'ansigreen': color_cls.GREEN, 'ansiyellow': color_cls.YELLOW, 'ansiblue': color_cls.BLUE, 'ansimagenta': color_cls.MAGENTA, 'ansicyan': color_cls.CYAN, # High intensity. 'ansibrightred': color_cls.RED | color_cls.INTENSITY, 'ansibrightgreen': color_cls.GREEN | color_cls.INTENSITY, 'ansibrightyellow': color_cls.YELLOW | color_cls.INTENSITY, 'ansibrightblue': color_cls.BLUE | color_cls.INTENSITY, 'ansibrightmagenta': color_cls.MAGENTA | color_cls.INTENSITY, 'ansibrightcyan': color_cls.CYAN | color_cls.INTENSITY, } FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR) BG_ANSI_COLORS = _create_ansi_color_dict(BACKGROUND_COLOR) assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) class ColorLookupTable(object): """ Inspired by pygments/formatters/terminal256.py """ def __init__(self): self._win32_colors = self._build_color_table() self.best_match = {} # Cache @staticmethod def _build_color_table(): """ Build an RGB-to-256 color conversion table """ FG = FOREGROUND_COLOR BG = BACKGROUND_COLOR return [ (0x00, 0x00, 0x00, FG.BLACK, BG.BLACK), (0x00, 0x00, 0xaa, FG.BLUE, BG.BLUE), (0x00, 0xaa, 0x00, FG.GREEN, BG.GREEN), (0x00, 0xaa, 0xaa, FG.CYAN, BG.CYAN), (0xaa, 0x00, 0x00, FG.RED, BG.RED), (0xaa, 0x00, 0xaa, FG.MAGENTA, BG.MAGENTA), (0xaa, 0xaa, 0x00, FG.YELLOW, BG.YELLOW), (0x88, 0x88, 0x88, FG.GRAY, BG.GRAY), (0x44, 0x44, 0xff, FG.BLUE | FG.INTENSITY, BG.BLUE | BG.INTENSITY), (0x44, 0xff, 0x44, FG.GREEN | FG.INTENSITY, BG.GREEN | BG.INTENSITY), (0x44, 0xff, 0xff, FG.CYAN | FG.INTENSITY, BG.CYAN | BG.INTENSITY), (0xff, 0x44, 0x44, FG.RED | FG.INTENSITY, BG.RED | BG.INTENSITY), (0xff, 0x44, 0xff, FG.MAGENTA | FG.INTENSITY, BG.MAGENTA | BG.INTENSITY), (0xff, 0xff, 0x44, FG.YELLOW | FG.INTENSITY, BG.YELLOW | BG.INTENSITY), (0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY), (0xff, 0xff, 0xff, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY), ] def _closest_color(self, r, g, b): distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) fg_match = 0 bg_match = 0 for r_, g_, b_, fg_, bg_ in self._win32_colors: rd = r - r_ gd = g - g_ bd = b - b_ d = rd * rd + gd * gd + bd * bd if d < distance: fg_match = fg_ bg_match = bg_ distance = d return fg_match, bg_match def _color_indexes(self, color): indexes = self.best_match.get(color, None) if indexes is None: try: rgb = int(str(color), 16) except ValueError: rgb = 0 r = (rgb >> 16) & 0xff g = (rgb >> 8) & 0xff b = rgb & 0xff indexes = self._closest_color(r, g, b) self.best_match[color] = indexes return indexes def lookup_fg_color(self, fg_color): """ Return the color for use in the `windll.kernel32.SetConsoleTextAttribute` API call. :param fg_color: Foreground as text. E.g. 'ffffff' or 'red' """ # Foreground. if fg_color in FG_ANSI_COLORS: return FG_ANSI_COLORS[fg_color] else: return self._color_indexes(fg_color)[0] def lookup_bg_color(self, bg_color): """ Return the color for use in the `windll.kernel32.SetConsoleTextAttribute` API call. :param bg_color: Background as text. E.g. 'ffffff' or 'red' """ # Background. if bg_color in BG_ANSI_COLORS: return BG_ANSI_COLORS[bg_color] else: return self._color_indexes(bg_color)[1] prompt_toolkit-2.0.10/prompt_toolkit/output/windows10.py0000644000175100017510000000512713545407204025154 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from ctypes import byref, windll from ctypes.wintypes import DWORD, HANDLE from prompt_toolkit.renderer import Output from prompt_toolkit.utils import is_windows from prompt_toolkit.win32_types import STD_OUTPUT_HANDLE from .vt100 import Vt100_Output from .win32 import Win32Output __all__ = [ 'Windows10_Output', ] # See: https://msdn.microsoft.com/pl-pl/library/windows/desktop/ms686033(v=vs.85).aspx ENABLE_PROCESSED_INPUT = 0x0001 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 class Windows10_Output(object): """ Windows 10 output abstraction. This enables and uses vt100 escape sequences. """ def __init__(self, stdout): self.win32_output = Win32Output(stdout) self.vt100_output = Vt100_Output(stdout, lambda: None) self._hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) def flush(self): """ Write to output stream and flush. """ original_mode = DWORD(0) # Remember the previous console mode. windll.kernel32.GetConsoleMode(self._hconsole, byref(original_mode)) # Enable processing of vt100 sequences. windll.kernel32.SetConsoleMode(self._hconsole, DWORD( ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) try: self.vt100_output.flush() finally: # Restore console mode. windll.kernel32.SetConsoleMode(self._hconsole, original_mode) def __getattr__(self, name): if name in ('get_size', 'get_rows_below_cursor_position', 'enable_mouse_support', 'disable_mouse_support', 'scroll_buffer_to_prompt', 'get_win32_screen_buffer_info', 'enable_bracketed_paste', 'disable_bracketed_paste'): return getattr(self.win32_output, name) else: return getattr(self.vt100_output, name) Output.register(Windows10_Output) def is_win_vt100_enabled(): """ Returns True when we're running Windows and VT100 escape sequences are supported. """ if not is_windows(): return False hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) # Get original console mode. original_mode = DWORD(0) windll.kernel32.GetConsoleMode(hconsole, byref(original_mode)) try: # Try to enable VT100 sequences. result = windll.kernel32.SetConsoleMode(hconsole, DWORD( ENABLE_PROCESSED_INPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return result == 1 finally: windll.kernel32.SetConsoleMode(hconsole, original_mode) prompt_toolkit-2.0.10/prompt_toolkit/patch_stdout.py0000644000175100017510000001062413545407204024460 0ustar jonathanjonathan00000000000000""" patch_stdout ============ This implements a context manager that ensures that print statements within it won't destroy the user interface. The context manager will replace `sys.stdout` by something that draws the output above the current prompt, rather than overwriting the UI. Usage:: with patch_stdout(): ... application.run() ... Multiple applications can run in the body of the context manager, one after the other. """ from __future__ import unicode_literals import sys import threading from contextlib import contextmanager from .application import run_in_terminal from .eventloop import get_event_loop __all__ = [ 'patch_stdout', 'StdoutProxy', ] @contextmanager def patch_stdout(raw=False): """ Replace `sys.stdout` by an :class:`_StdoutProxy` instance. Writing to this proxy will make sure that the text appears above the prompt, and that it doesn't destroy the output from the renderer. If no application is curring, the behaviour should be identical to writing to `sys.stdout` directly. :param raw: (`bool`) When True, vt100 terminal escape sequences are not removed/escaped. """ proxy = StdoutProxy(raw=raw) original_stdout = sys.stdout original_stderr = sys.stderr # Enter. sys.stdout = proxy sys.stderr = proxy try: yield finally: # Exit. proxy.flush() sys.stdout = original_stdout sys.stderr = original_stderr class StdoutProxy(object): """ Proxy object for stdout which captures everything and prints output above the current application. """ def __init__(self, raw=False, original_stdout=None): assert isinstance(raw, bool) original_stdout = original_stdout or sys.__stdout__ self.original_stdout = original_stdout self._lock = threading.RLock() self._raw = raw self._buffer = [] # errors/encoding attribute for compatibility with sys.__stdout__. self.errors = original_stdout.errors self.encoding = original_stdout.encoding def _write_and_flush(self, text): """ Write the given text to stdout and flush. If an application is running, use `run_in_terminal`. """ if not text: # Don't bother calling `run_in_terminal` when there is nothing to # display. return def write_and_flush(): self.original_stdout.write(text) self.original_stdout.flush() def write_and_flush_in_loop(): # If an application is running, use `run_in_terminal`, otherwise # call it directly. run_in_terminal(write_and_flush, in_executor=False) # Make sure `write_and_flush` is executed *in* the event loop, not in # another thread. get_event_loop().call_from_executor(write_and_flush_in_loop) def _write(self, data): """ Note: print()-statements cause to multiple write calls. (write('line') and write('\n')). Of course we don't want to call `run_in_terminal` for every individual call, because that's too expensive, and as long as the newline hasn't been written, the text itself is again overwritten by the rendering of the input command line. Therefor, we have a little buffer which holds the text until a newline is written to stdout. """ if '\n' in data: # When there is a newline in the data, write everything before the # newline, including the newline itself. before, after = data.rsplit('\n', 1) to_write = self._buffer + [before, '\n'] self._buffer = [after] text = ''.join(to_write) self._write_and_flush(text) else: # Otherwise, cache in buffer. self._buffer.append(data) def _flush(self): text = ''.join(self._buffer) self._buffer = [] self._write_and_flush(text) def write(self, data): with self._lock: self._write(data) def flush(self): """ Flush buffered output. """ with self._lock: self._flush() def fileno(self): """ Return file descriptor. """ # This is important for code that expects sys.stdout.fileno() to work. return self.original_stdout.fileno() prompt_toolkit-2.0.10/prompt_toolkit/renderer.py0000644000175100017510000006154613545407204023576 0ustar jonathanjonathan00000000000000""" Renders the command line on the console. (Redraws parts of the input line that were changed.) """ from __future__ import unicode_literals import threading import time from collections import deque from six.moves import range from prompt_toolkit.eventloop import ( From, Future, ensure_future, get_event_loop, ) from prompt_toolkit.filters import to_filter from prompt_toolkit.formatted_text import to_formatted_text from prompt_toolkit.input.base import Input from prompt_toolkit.layout.mouse_handlers import MouseHandlers from prompt_toolkit.layout.screen import Point, Screen, WritePosition from prompt_toolkit.output import ColorDepth, Output from prompt_toolkit.styles import ( BaseStyle, DummyStyleTransformation, StyleTransformation, ) from prompt_toolkit.utils import is_windows __all__ = [ 'Renderer', 'print_formatted_text', ] def _output_screen_diff(app, output, screen, current_pos, color_depth, previous_screen=None, last_style=None, is_done=False, full_screen=False, attrs_for_style_string=None, size=None, previous_width=0): # XXX: drop is_done """ Render the diff between this screen and the previous screen. This takes two `Screen` instances. The one that represents the output like it was during the last rendering and one that represents the current output raster. Looking at these two `Screen` instances, this function will render the difference by calling the appropriate methods of the `Output` object that only paint the changes to the terminal. This is some performance-critical code which is heavily optimized. Don't change things without profiling first. :param current_pos: Current cursor position. :param last_style: The style string, used for drawing the last drawn character. (Color/attributes.) :param attrs_for_style_string: :class:`._StyleStringToAttrsCache` instance. :param width: The width of the terminal. :param previous_width: The width of the terminal during the last rendering. """ width, height = size.columns, size.rows #: Remember the last printed character. last_style = [last_style] # nonlocal #: Variable for capturing the output. write = output.write write_raw = output.write_raw # Create locals for the most used output methods. # (Save expensive attribute lookups.) _output_set_attributes = output.set_attributes _output_reset_attributes = output.reset_attributes _output_cursor_forward = output.cursor_forward _output_cursor_up = output.cursor_up _output_cursor_backward = output.cursor_backward # Hide cursor before rendering. (Avoid flickering.) output.hide_cursor() def reset_attributes(): " Wrapper around Output.reset_attributes. " _output_reset_attributes() last_style[0] = None # Forget last char after resetting attributes. def move_cursor(new): " Move cursor to this `new` point. Returns the given Point. " current_x, current_y = current_pos.x, current_pos.y if new.y > current_y: # Use newlines instead of CURSOR_DOWN, because this might add new lines. # CURSOR_DOWN will never create new lines at the bottom. # Also reset attributes, otherwise the newline could draw a # background color. reset_attributes() write('\r\n' * (new.y - current_y)) current_x = 0 _output_cursor_forward(new.x) return new elif new.y < current_y: _output_cursor_up(current_y - new.y) if current_x >= width - 1: write('\r') _output_cursor_forward(new.x) elif new.x < current_x or current_x >= width - 1: _output_cursor_backward(current_x - new.x) elif new.x > current_x: _output_cursor_forward(new.x - current_x) return new def output_char(char): """ Write the output of this character. """ # If the last printed character has the same style, don't output the # style again. the_last_style = last_style[0] # Either `None` or a style string. if the_last_style == char.style: write(char.char) else: # Look up `Attr` for this style string. Only set attributes if different. # (Two style strings can still have the same formatting.) # Note that an empty style string can have formatting that needs to # be applied, because of style transformations. new_attrs = attrs_for_style_string[char.style] if not the_last_style or new_attrs != attrs_for_style_string[the_last_style]: _output_set_attributes(new_attrs, color_depth) write(char.char) last_style[0] = char.style # Render for the first time: reset styling. if not previous_screen: reset_attributes() # Disable autowrap. (When entering a the alternate screen, or anytime when # we have a prompt. - In the case of a REPL, like IPython, people can have # background threads, and it's hard for debugging if their output is not # wrapped.) if not previous_screen or not full_screen: output.disable_autowrap() # When the previous screen has a different size, redraw everything anyway. # Also when we are done. (We might take up less rows, so clearing is important.) if is_done or not previous_screen or previous_width != width: # XXX: also consider height?? current_pos = move_cursor(Point(x=0, y=0)) reset_attributes() output.erase_down() previous_screen = Screen() # Get height of the screen. # (height changes as we loop over data_buffer, so remember the current value.) # (Also make sure to clip the height to the size of the output.) current_height = min(screen.height, height) # Loop over the rows. row_count = min(max(screen.height, previous_screen.height), height) c = 0 # Column counter. for y in range(row_count): new_row = screen.data_buffer[y] previous_row = previous_screen.data_buffer[y] zero_width_escapes_row = screen.zero_width_escapes[y] new_max_line_len = min(width - 1, max(new_row.keys()) if new_row else 0) previous_max_line_len = min(width - 1, max(previous_row.keys()) if previous_row else 0) # Loop over the columns. c = 0 while c < new_max_line_len + 1: new_char = new_row[c] old_char = previous_row[c] char_width = (new_char.width or 1) # When the old and new character at this position are different, # draw the output. (Because of the performance, we don't call # `Char.__ne__`, but inline the same expression.) if new_char.char != old_char.char or new_char.style != old_char.style: current_pos = move_cursor(Point(x=c, y=y)) # Send injected escape sequences to output. if c in zero_width_escapes_row: write_raw(zero_width_escapes_row[c]) output_char(new_char) current_pos = Point(x=current_pos.x + char_width, y=current_pos.y) c += char_width # If the new line is shorter, trim it. if previous_screen and new_max_line_len < previous_max_line_len: current_pos = move_cursor(Point(x=new_max_line_len + 1, y=y)) reset_attributes() output.erase_end_of_line() # Correctly reserve vertical space as required by the layout. # When this is a new screen (drawn for the first time), or for some reason # higher than the previous one. Move the cursor once to the bottom of the # output. That way, we're sure that the terminal scrolls up, even when the # lower lines of the canvas just contain whitespace. # The most obvious reason that we actually want this behaviour is the avoid # the artifact of the input scrolling when the completion menu is shown. # (If the scrolling is actually wanted, the layout can still be build in a # way to behave that way by setting a dynamic height.) if current_height > previous_screen.height: current_pos = move_cursor(Point(x=0, y=current_height - 1)) # Move cursor: if is_done: current_pos = move_cursor(Point(x=0, y=current_height)) output.erase_down() else: current_pos = move_cursor( screen.get_cursor_position(app.layout.current_window)) if is_done or not full_screen: output.enable_autowrap() # Always reset the color attributes. This is important because a background # thread could print data to stdout and we want that to be displayed in the # default colors. (Also, if a background color has been set, many terminals # give weird artifacts on resize events.) reset_attributes() if screen.show_cursor or is_done: output.show_cursor() return current_pos, last_style[0] class HeightIsUnknownError(Exception): " Information unavailable. Did not yet receive the CPR response. " class _StyleStringToAttrsCache(dict): """ A cache structure that maps style strings to :class:`.Attr`. (This is an important speed up.) """ def __init__(self, get_attrs_for_style_str, style_transformation): assert callable(get_attrs_for_style_str) assert isinstance(style_transformation, StyleTransformation) self.get_attrs_for_style_str = get_attrs_for_style_str self.style_transformation = style_transformation def __missing__(self, style_str): attrs = self.get_attrs_for_style_str(style_str) attrs = self.style_transformation.transform_attrs(attrs) self[style_str] = attrs return attrs class CPR_Support(object): " Enum: whether or not CPR is supported. " SUPPORTED = 'SUPPORTED' NOT_SUPPORTED = 'NOT_SUPPORTED' UNKNOWN = 'UNKNOWN' class Renderer(object): """ Typical usage: :: output = Vt100_Output.from_pty(sys.stdout) r = Renderer(style, output) r.render(app, layout=...) """ CPR_TIMEOUT = 2 # Time to wait until we consider CPR to be not supported. def __init__(self, style, output, input, full_screen=False, mouse_support=False, cpr_not_supported_callback=None): assert isinstance(style, BaseStyle) assert isinstance(output, Output) assert isinstance(input, Input) assert callable(cpr_not_supported_callback) or cpr_not_supported_callback is None self.style = style self.output = output self.input = input self.full_screen = full_screen self.mouse_support = to_filter(mouse_support) self.cpr_not_supported_callback = cpr_not_supported_callback self._in_alternate_screen = False self._mouse_support_enabled = False self._bracketed_paste_enabled = False # Future set when we are waiting for a CPR flag. self._waiting_for_cpr_futures = deque() self.cpr_support = CPR_Support.UNKNOWN if not input.responds_to_cpr: self.cpr_support = CPR_Support.NOT_SUPPORTED # Cache for the style. self._attrs_for_style = None self._last_style_hash = None self._last_transformation_hash = None self._last_color_depth = None self.reset(_scroll=True) def reset(self, _scroll=False, leave_alternate_screen=True): # Reset position self._cursor_pos = Point(x=0, y=0) # Remember the last screen instance between renderers. This way, # we can create a `diff` between two screens and only output the # difference. It's also to remember the last height. (To show for # instance a toolbar at the bottom position.) self._last_screen = None self._last_size = None self._last_style = None # Default MouseHandlers. (Just empty.) self.mouse_handlers = MouseHandlers() #: Space from the top of the layout, until the bottom of the terminal. #: We don't know this until a `report_absolute_cursor_row` call. self._min_available_height = 0 # In case of Windows, also make sure to scroll to the current cursor # position. (Only when rendering the first time.) if is_windows() and _scroll: self.output.scroll_buffer_to_prompt() # Quit alternate screen. if self._in_alternate_screen and leave_alternate_screen: self.output.quit_alternate_screen() self._in_alternate_screen = False # Disable mouse support. if self._mouse_support_enabled: self.output.disable_mouse_support() self._mouse_support_enabled = False # Disable bracketed paste. if self._bracketed_paste_enabled: self.output.disable_bracketed_paste() self._bracketed_paste_enabled = False # Flush output. `disable_mouse_support` needs to write to stdout. self.output.flush() @property def last_rendered_screen(self): """ The `Screen` class that was generated during the last rendering. This can be `None`. """ return self._last_screen @property def height_is_known(self): """ True when the height from the cursor until the bottom of the terminal is known. (It's often nicer to draw bottom toolbars only if the height is known, in order to avoid flickering when the CPR response arrives.) """ return self.full_screen or self._min_available_height > 0 or \ is_windows() # On Windows, we don't have to wait for a CPR. @property def rows_above_layout(self): """ Return the number of rows visible in the terminal above the layout. """ if self._in_alternate_screen: return 0 elif self._min_available_height > 0: total_rows = self.output.get_size().rows last_screen_height = self._last_screen.height if self._last_screen else 0 return total_rows - max(self._min_available_height, last_screen_height) else: raise HeightIsUnknownError('Rows above layout is unknown.') def request_absolute_cursor_position(self): """ Get current cursor position. We do this to calculate the minimum available height that we can consume for rendering the prompt. This is the available space below te cursor. For vt100: Do CPR request. (answer will arrive later.) For win32: Do API call. (Answer comes immediately.) """ # Only do this request when the cursor is at the top row. (after a # clear or reset). We will rely on that in `report_absolute_cursor_row`. assert self._cursor_pos.y == 0 # In full-screen mode, always use the total height as min-available-height. if self.full_screen: self._min_available_height = self.output.get_size().rows # For Win32, we have an API call to get the number of rows below the # cursor. elif is_windows(): self._min_available_height = self.output.get_rows_below_cursor_position() # Use CPR. else: if self.cpr_support == CPR_Support.NOT_SUPPORTED: return def do_cpr(): # Asks for a cursor position report (CPR). self._waiting_for_cpr_futures.append(Future()) self.output.ask_for_cpr() if self.cpr_support == CPR_Support.SUPPORTED: do_cpr() # If we don't know whether CPR is supported, only do a request if # none is pending, and test it, using a timer. elif self.cpr_support == CPR_Support.UNKNOWN and not self.waiting_for_cpr: do_cpr() def timer(): time.sleep(self.CPR_TIMEOUT) # Not set in the meantime -> not supported. if self.cpr_support == CPR_Support.UNKNOWN: self.cpr_support = CPR_Support.NOT_SUPPORTED if self.cpr_not_supported_callback: # Make sure to call this callback in the main thread. get_event_loop().call_from_executor(self.cpr_not_supported_callback) t = threading.Thread(target=timer) t.daemon = True t.start() def report_absolute_cursor_row(self, row): """ To be called when we know the absolute cursor position. (As an answer of a "Cursor Position Request" response.) """ self.cpr_support = CPR_Support.SUPPORTED # Calculate the amount of rows from the cursor position until the # bottom of the terminal. total_rows = self.output.get_size().rows rows_below_cursor = total_rows - row + 1 # Set the minimum available height. self._min_available_height = rows_below_cursor # Pop and set waiting for CPR future. try: f = self._waiting_for_cpr_futures.popleft() except IndexError: pass # Received CPR response without having a CPR. else: f.set_result(None) @property def waiting_for_cpr(self): """ Waiting for CPR flag. True when we send the request, but didn't got a response. """ return bool(self._waiting_for_cpr_futures) def wait_for_cpr_responses(self, timeout=1): """ Wait for a CPR response. """ cpr_futures = list(self._waiting_for_cpr_futures) # Make copy. # When there are no CPRs in the queue. Don't do anything. if not cpr_futures or self.cpr_support == CPR_Support.NOT_SUPPORTED: return Future.succeed(None) f = Future() # When a CPR has been received, set the result. def wait_for_responses(): for response_f in cpr_futures: yield From(response_f) if not f.done(): f.set_result(None) ensure_future(wait_for_responses()) # Timeout. def wait_for_timeout(): time.sleep(timeout) # Got timeout. if not f.done(): self._waiting_for_cpr_futures = deque() f.set_result(None) t = threading.Thread(target=wait_for_timeout) t.daemon = True t.start() return f def render(self, app, layout, is_done=False): """ Render the current interface to the output. :param is_done: When True, put the cursor at the end of the interface. We won't print any changes to this part. """ output = self.output # Enter alternate screen. if self.full_screen and not self._in_alternate_screen: self._in_alternate_screen = True output.enter_alternate_screen() # Enable bracketed paste. if not self._bracketed_paste_enabled: self.output.enable_bracketed_paste() self._bracketed_paste_enabled = True # Enable/disable mouse support. needs_mouse_support = self.mouse_support() if needs_mouse_support and not self._mouse_support_enabled: output.enable_mouse_support() self._mouse_support_enabled = True elif not needs_mouse_support and self._mouse_support_enabled: output.disable_mouse_support() self._mouse_support_enabled = False # Create screen and write layout to it. size = output.get_size() screen = Screen() screen.show_cursor = False # Hide cursor by default, unless one of the # containers decides to display it. mouse_handlers = MouseHandlers() # Calculate height. if self.full_screen: height = size.rows elif is_done: # When we are done, we don't necessary want to fill up until the bottom. height = layout.container.preferred_height(size.columns, size.rows).preferred else: last_height = self._last_screen.height if self._last_screen else 0 height = max(self._min_available_height, last_height, layout.container.preferred_height(size.columns, size.rows).preferred) height = min(height, size.rows) # When te size changes, don't consider the previous screen. if self._last_size != size: self._last_screen = None # When we render using another style or another color depth, do a full # repaint. (Forget about the previous rendered screen.) # (But note that we still use _last_screen to calculate the height.) if (self.style.invalidation_hash() != self._last_style_hash or app.style_transformation.invalidation_hash() != self._last_transformation_hash or app.color_depth != self._last_color_depth): self._last_screen = None self._attrs_for_style = None if self._attrs_for_style is None: self._attrs_for_style = _StyleStringToAttrsCache( self.style.get_attrs_for_style_str, app.style_transformation) self._last_style_hash = self.style.invalidation_hash() self._last_transformation_hash = app.style_transformation.invalidation_hash() self._last_color_depth = app.color_depth layout.container.write_to_screen(screen, mouse_handlers, WritePosition( xpos=0, ypos=0, width=size.columns, height=height, ), parent_style='', erase_bg=False, z_index=None) screen.draw_all_floats() # When grayed. Replace all styles in the new screen. if app.exit_style: screen.append_style_to_content(app.exit_style) # Process diff and write to output. self._cursor_pos, self._last_style = _output_screen_diff( app, output, screen, self._cursor_pos, app.color_depth, self._last_screen, self._last_style, is_done, full_screen=self.full_screen, attrs_for_style_string=self._attrs_for_style, size=size, previous_width=(self._last_size.columns if self._last_size else 0)) self._last_screen = screen self._last_size = size self.mouse_handlers = mouse_handlers output.flush() # Set visible windows in layout. app.layout.visible_windows = screen.visible_windows if is_done: self.reset() def erase(self, leave_alternate_screen=True): """ Hide all output and put the cursor back at the first line. This is for instance used for running a system command (while hiding the CLI) and later resuming the same CLI.) :param leave_alternate_screen: When True, and when inside an alternate screen buffer, quit the alternate screen. """ output = self.output output.cursor_backward(self._cursor_pos.x) output.cursor_up(self._cursor_pos.y) output.erase_down() output.reset_attributes() output.enable_autowrap() output.flush() self.reset(leave_alternate_screen=leave_alternate_screen) def clear(self): """ Clear screen and go to 0,0 """ # Erase current output first. self.erase() # Send "Erase Screen" command and go to (0, 0). output = self.output output.erase_screen() output.cursor_goto(0, 0) output.flush() self.request_absolute_cursor_position() def print_formatted_text( output, formatted_text, style, style_transformation=None, color_depth=None): """ Print a list of (style_str, text) tuples in the given style to the output. """ assert isinstance(output, Output) assert isinstance(style, BaseStyle) assert style_transformation is None or isinstance(style_transformation, StyleTransformation) assert color_depth is None or color_depth in ColorDepth._ALL fragments = to_formatted_text(formatted_text) style_transformation = style_transformation or DummyStyleTransformation() color_depth = color_depth or ColorDepth.default() # Reset first. output.reset_attributes() output.enable_autowrap() # Print all (style_str, text) tuples. attrs_for_style_string = _StyleStringToAttrsCache( style.get_attrs_for_style_str, style_transformation) for style_str, text in fragments: attrs = attrs_for_style_string[style_str] if attrs: output.set_attributes(attrs, color_depth) else: output.reset_attributes() # Eliminate carriage returns text = text.replace('\r', '') # Assume that the output is raw, and insert a carriage return before # every newline. (Also important when the front-end is a telnet client.) output.write(text.replace('\n', '\r\n')) # Reset again. output.reset_attributes() output.flush() prompt_toolkit-2.0.10/prompt_toolkit/search.py0000644000175100017510000001405413545407204023225 0ustar jonathanjonathan00000000000000""" Search operations. For the key bindings implementation with attached filters, check `prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings instead of calling these function directly.) """ from __future__ import unicode_literals import six from .application.current import get_app from .filters import is_searching, to_filter from .key_binding.vi_state import InputMode __all__ = [ 'SearchDirection', 'start_search', 'stop_search', ] class SearchDirection(object): FORWARD = 'FORWARD' BACKWARD = 'BACKWARD' _ALL = [FORWARD, BACKWARD] class SearchState(object): """ A search 'query', associated with a search field (like a SearchToolbar). Every searchable `BufferControl` points to a `search_buffer_control` (another `BufferControls`) which represents the search field. The `SearchState` attached to that search field is used for storing the current search query. It is possible to have one searchfield for multiple `BufferControls`. In that case, they'll share the same `SearchState`. If there are multiple `BufferControls` that display the same `Buffer`, then they can have a different `SearchState` each (if they have a different search control). """ __slots__ = ('text', 'direction', 'ignore_case') def __init__(self, text='', direction=SearchDirection.FORWARD, ignore_case=False): assert isinstance(text, six.text_type) assert direction in (SearchDirection.FORWARD, SearchDirection.BACKWARD) ignore_case = to_filter(ignore_case) self.text = text self.direction = direction self.ignore_case = ignore_case def __repr__(self): return '%s(%r, direction=%r, ignore_case=%r)' % ( self.__class__.__name__, self.text, self.direction, self.ignore_case) def __invert__(self): """ Create a new SearchState where backwards becomes forwards and the other way around. """ if self.direction == SearchDirection.BACKWARD: direction = SearchDirection.FORWARD else: direction = SearchDirection.BACKWARD return SearchState(text=self.text, direction=direction, ignore_case=self.ignore_case) def start_search(buffer_control=None, direction=SearchDirection.FORWARD): """ Start search through the given `buffer_control` using the `search_buffer_control`. :param buffer_control: Start search for this `BufferControl`. If not given, search through the current control. """ from prompt_toolkit.layout.controls import BufferControl assert buffer_control is None or isinstance(buffer_control, BufferControl) assert direction in SearchDirection._ALL layout = get_app().layout # When no control is given, use the current control if that's a BufferControl. if buffer_control is None: if not isinstance(layout.current_control, BufferControl): return buffer_control = layout.current_control # Only if this control is searchable. search_buffer_control = buffer_control.search_buffer_control if search_buffer_control: buffer_control.search_state.direction = direction # Make sure to focus the search BufferControl layout.focus(search_buffer_control) # Remember search link. layout.search_links[search_buffer_control] = buffer_control # If we're in Vi mode, make sure to go into insert mode. get_app().vi_state.input_mode = InputMode.INSERT def stop_search(buffer_control=None): """ Stop search through the given `buffer_control`. """ from prompt_toolkit.layout.controls import BufferControl assert buffer_control is None or isinstance(buffer_control, BufferControl) layout = get_app().layout if buffer_control is None: buffer_control = layout.search_target_buffer_control search_buffer_control = buffer_control.search_buffer_control else: assert buffer_control in layout.search_links.values() search_buffer_control = _get_reverse_search_links(layout)[buffer_control] # Focus the original buffer again. layout.focus(buffer_control) # Remove the search link. del layout.search_links[search_buffer_control] # Reset content of search control. search_buffer_control.buffer.reset() # If we're in Vi mode, go back to navigation mode. get_app().vi_state.input_mode = InputMode.NAVIGATION def do_incremental_search(direction, count=1): """ Apply search, but keep search buffer focused. """ assert is_searching() assert direction in SearchDirection._ALL layout = get_app().layout search_control = layout.current_control prev_control = layout.search_target_buffer_control search_state = prev_control.search_state # Update search_state. direction_changed = search_state.direction != direction search_state.text = search_control.buffer.text search_state.direction = direction # Apply search to current buffer. if not direction_changed: prev_control.buffer.apply_search( search_state, include_current_position=False, count=count) def accept_search(): """ Accept current search query. Focus original `BufferControl` again. """ layout = get_app().layout search_control = layout.current_control target_buffer_control = layout.search_target_buffer_control search_state = target_buffer_control.search_state # Update search state. if search_control.buffer.text: search_state.text = search_control.buffer.text # Apply search. target_buffer_control.buffer.apply_search(search_state, include_current_position=True) # Add query to history of search line. search_control.buffer.append_to_history() # Stop search and focus previous control again. stop_search(target_buffer_control) def _get_reverse_search_links(layout): """ Return mapping from BufferControl to SearchBufferControl. """ return dict((buffer_control, search_buffer_control) for search_buffer_control, buffer_control in layout.search_links.items()) prompt_toolkit-2.0.10/prompt_toolkit/selection.py0000644000175100017510000000214013545407204023736 0ustar jonathanjonathan00000000000000""" Data structures for the selection. """ from __future__ import unicode_literals __all__ = [ 'SelectionType', 'PasteMode', 'SelectionState', ] class SelectionType(object): """ Type of selection. """ #: Characters. (Visual in Vi.) CHARACTERS = 'CHARACTERS' #: Whole lines. (Visual-Line in Vi.) LINES = 'LINES' #: A block selection. (Visual-Block in Vi.) BLOCK = 'BLOCK' class PasteMode(object): EMACS = 'EMACS' # Yank like emacs. VI_AFTER = 'VI_AFTER' # When pressing 'p' in Vi. VI_BEFORE = 'VI_BEFORE' # When pressing 'P' in Vi. class SelectionState(object): """ State of the current selection. :param original_cursor_position: int :param type: :class:`~.SelectionType` """ def __init__(self, original_cursor_position=0, type=SelectionType.CHARACTERS): self.original_cursor_position = original_cursor_position self.type = type def __repr__(self): return '%s(original_cursor_position=%r, type=%r)' % ( self.__class__.__name__, self.original_cursor_position, self.type) prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/0000755000175100017510000000000013545410361023436 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/__init__.py0000644000175100017510000000153513545407204025555 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .dialogs import ( button_dialog, input_dialog, message_dialog, progress_dialog, radiolist_dialog, yes_no_dialog, ) from .progress_bar import ProgressBar from .prompt import ( CompleteStyle, PromptSession, confirm, create_confirm_session, prompt, ) from .utils import ( clear, clear_title, print_container, print_formatted_text, set_title, ) __all__ = [ # Dialogs. 'input_dialog', 'message_dialog', 'progress_dialog', 'radiolist_dialog', 'yes_no_dialog', 'button_dialog', # Prompts. 'PromptSession', 'prompt', 'confirm', 'create_confirm_session', 'CompleteStyle', # Progress bars. 'ProgressBar', # Utils. 'clear', 'clear_title', 'print_formatted_text', 'set_title', ] prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/dialogs.py0000644000175100017510000001442713545407204025444 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import functools from prompt_toolkit.application import Application from prompt_toolkit.application.current import get_app from prompt_toolkit.eventloop import run_in_executor from prompt_toolkit.key_binding.bindings.focus import ( focus_next, focus_previous, ) from prompt_toolkit.key_binding.defaults import load_key_bindings from prompt_toolkit.key_binding.key_bindings import ( KeyBindings, merge_key_bindings, ) from prompt_toolkit.layout import Layout from prompt_toolkit.layout.containers import HSplit from prompt_toolkit.layout.dimension import Dimension as D from prompt_toolkit.widgets import ( Box, Button, Dialog, Label, ProgressBar, RadioList, TextArea, ) __all__ = [ 'yes_no_dialog', 'button_dialog', 'input_dialog', 'message_dialog', 'radiolist_dialog', 'progress_dialog', ] def yes_no_dialog(title='', text='', yes_text='Yes', no_text='No', style=None, async_=False): """ Display a Yes/No dialog. Return a boolean. """ def yes_handler(): get_app().exit(result=True) def no_handler(): get_app().exit(result=False) dialog = Dialog( title=title, body=Label(text=text, dont_extend_height=True), buttons=[ Button(text=yes_text, handler=yes_handler), Button(text=no_text, handler=no_handler), ], with_background=True) return _run_dialog(dialog, style, async_=async_) def button_dialog(title='', text='', buttons=[], style=None, async_=False): """ Display a dialog with button choices (given as a list of tuples). Return the value associated with button. """ def button_handler(v): get_app().exit(result=v) dialog = Dialog( title=title, body=Label(text=text, dont_extend_height=True), buttons=[Button(text=t, handler=functools.partial(button_handler, v)) for t, v in buttons], with_background=True) return _run_dialog(dialog, style, async_=async_) def input_dialog(title='', text='', ok_text='OK', cancel_text='Cancel', completer=None, password=False, style=None, async_=False): """ Display a text input box. Return the given text, or None when cancelled. """ def accept(buf): get_app().layout.focus(ok_button) return True # Keep text. def ok_handler(): get_app().exit(result=textfield.text) ok_button = Button(text=ok_text, handler=ok_handler) cancel_button = Button(text=cancel_text, handler=_return_none) textfield = TextArea( multiline=False, password=password, completer=completer, accept_handler=accept) dialog = Dialog( title=title, body=HSplit([ Label(text=text, dont_extend_height=True), textfield, ], padding=D(preferred=1, max=1)), buttons=[ok_button, cancel_button], with_background=True) return _run_dialog(dialog, style, async_=async_) def message_dialog(title='', text='', ok_text='Ok', style=None, async_=False): """ Display a simple message box and wait until the user presses enter. """ dialog = Dialog( title=title, body=Label(text=text, dont_extend_height=True), buttons=[ Button(text=ok_text, handler=_return_none), ], with_background=True) return _run_dialog(dialog, style, async_=async_) def radiolist_dialog(title='', text='', ok_text='Ok', cancel_text='Cancel', values=None, style=None, async_=False): """ Display a simple list of element the user can choose amongst. Only one element can be selected at a time using Arrow keys and Enter. The focus can be moved between the list and the Ok/Cancel button with tab. """ def ok_handler(): get_app().exit(result=radio_list.current_value) radio_list = RadioList(values) dialog = Dialog( title=title, body=HSplit([ Label(text=text, dont_extend_height=True), radio_list, ], padding=1), buttons=[ Button(text=ok_text, handler=ok_handler), Button(text=cancel_text, handler=_return_none), ], with_background=True) return _run_dialog(dialog, style, async_=async_) def progress_dialog(title='', text='', run_callback=None, style=None, async_=False): """ :param run_callback: A function that receives as input a `set_percentage` function and it does the work. """ assert callable(run_callback) progressbar = ProgressBar() text_area = TextArea( focusable=False, # Prefer this text area as big as possible, to avoid having a window # that keeps resizing when we add text to it. height=D(preferred=10**10)) dialog = Dialog( body=HSplit([ Box(Label(text=text)), Box(text_area, padding=D.exact(1)), progressbar, ]), title=title, with_background=True) app = _create_app(dialog, style) def set_percentage(value): progressbar.percentage = int(value) app.invalidate() def log_text(text): text_area.buffer.insert_text(text) app.invalidate() # Run the callback in the executor. When done, set a return value for the # UI, so that it quits. def start(): try: run_callback(set_percentage, log_text) finally: app.exit() run_in_executor(start) if async_: return app.run_async() else: return app.run() def _run_dialog(dialog, style, async_=False): " Turn the `Dialog` into an `Application` and run it. " application = _create_app(dialog, style) if async_: return application.run_async() else: return application.run() def _create_app(dialog, style): # Key bindings. bindings = KeyBindings() bindings.add('tab')(focus_next) bindings.add('s-tab')(focus_previous) return Application( layout=Layout(dialog), key_bindings=merge_key_bindings([ load_key_bindings(), bindings, ]), mouse_support=True, style=style, full_screen=True) def _return_none(): " Button handler that returns None. " get_app().exit() prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/progress_bar/0000755000175100017510000000000013545410361026126 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/progress_bar/__init__.py0000644000175100017510000000076413545407204030250 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import ProgressBar from .formatters import ( Bar, Formatter, IterationsPerSecond, Label, Percentage, Progress, Rainbow, SpinningWheel, Text, TimeElapsed, TimeLeft, ) __all__ = [ 'ProgressBar', # Formatters. 'Formatter', 'Text', 'Label', 'Percentage', 'Bar', 'Progress', 'TimeElapsed', 'TimeLeft', 'IterationsPerSecond', 'SpinningWheel', 'Rainbow', ] prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/progress_bar/base.py0000644000175100017510000002377313545407204027430 0ustar jonathanjonathan00000000000000""" Progress bar implementation on top of prompt_toolkit. :: with ProgressBar(...) as pb: for item in pb(data): ... """ from __future__ import unicode_literals import contextlib import datetime import functools import os import signal import sys import threading import time import traceback from prompt_toolkit.application import Application from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.filters import Condition, is_done, renderer_height_is_known from prompt_toolkit.formatted_text import is_formatted_text, to_formatted_text from prompt_toolkit.input.defaults import get_default_input from prompt_toolkit.key_binding import KeyBindings from prompt_toolkit.layout import ( ConditionalContainer, FormattedTextControl, HSplit, Layout, VSplit, Window, ) from prompt_toolkit.layout.controls import UIContent, UIControl from prompt_toolkit.layout.dimension import D from prompt_toolkit.output.defaults import create_output from prompt_toolkit.styles import BaseStyle from prompt_toolkit.utils import in_main_thread from .formatters import Formatter, create_default_formatters __all__ = [ 'ProgressBar', ] def create_key_bindings(): """ Key bindings handled by the progress bar. (The main thread is not supposed to handle any key bindings.) """ kb = KeyBindings() @kb.add('c-l') def _(event): event.app.renderer.clear() @kb.add('c-c') def _(event): # Send KeyboardInterrupt to the main thread. os.kill(os.getpid(), signal.SIGINT) return kb class ProgressBar(object): """ Progress bar context manager. Usage :: with ProgressBar(...) as pb: for item in pb(data): ... :param title: Text to be displayed above the progress bars. This can be a callable or formatted text as well. :param formatters: List of :class:`.Formatter` instances. :param bottom_toolbar: Text to be displayed in the bottom toolbar. This can be a callable or formatted text. :param style: :class:`prompt_toolkit.styles.BaseStyle` instance. :param key_bindings: :class:`.KeyBindings` instance. :param file: The file object used for rendering, by default `sys.stderr` is used. :param color_depth: `prompt_toolkit` `ColorDepth` instance. :param output: :class:`~prompt_toolkit.output.Output` instance. :param input: :class:`~prompt_toolkit.input.Input` instance. """ def __init__(self, title=None, formatters=None, bottom_toolbar=None, style=None, key_bindings=None, file=None, color_depth=None, output=None, input=None): assert formatters is None or ( isinstance(formatters, list) and all(isinstance(fo, Formatter) for fo in formatters)) assert style is None or isinstance(style, BaseStyle) assert key_bindings is None or isinstance(key_bindings, KeyBindings) self.title = title self.formatters = formatters or create_default_formatters() self.bottom_toolbar = bottom_toolbar self.counters = [] self.style = style self.key_bindings = key_bindings # Note that we use __stderr__ as default error output, because that # works best with `patch_stdout`. self.color_depth = color_depth self.output = output or create_output(stdout=file or sys.__stderr__) self.input = input or get_default_input() self._thread = None self._loop = get_event_loop() self._previous_winch_handler = None self._has_sigwinch = False def __enter__(self): # Create UI Application. title_toolbar = ConditionalContainer( Window(FormattedTextControl(lambda: self.title), height=1, style='class:progressbar,title'), filter=Condition(lambda: self.title is not None)) bottom_toolbar = ConditionalContainer( Window(FormattedTextControl(lambda: self.bottom_toolbar, style='class:bottom-toolbar.text'), style='class:bottom-toolbar', height=1), filter=~is_done & renderer_height_is_known & Condition(lambda: self.bottom_toolbar is not None)) def width_for_formatter(formatter): # Needs to be passed as callable (partial) to the 'width' # parameter, because we want to call it on every resize. return formatter.get_width(progress_bar=self) progress_controls = [ Window( content=_ProgressControl(self, f), width=functools.partial(width_for_formatter, f)) for f in self.formatters ] self.app = Application( min_redraw_interval=.05, layout=Layout(HSplit([ title_toolbar, VSplit(progress_controls, height=lambda: D( preferred=len(self.counters), max=len(self.counters))), Window(), bottom_toolbar, ])), style=self.style, key_bindings=self.key_bindings, color_depth=self.color_depth, output=self.output, input=self.input) # Run application in different thread. def run(): with _auto_refresh_context(self.app, .3): try: self.app.run() except BaseException as e: traceback.print_exc() print(e) self._thread = threading.Thread(target=run) self._thread.start() # Attach WINCH signal handler in main thread. # (Interrupt that we receive during resize events.) self._has_sigwinch = hasattr(signal, 'SIGWINCH') and in_main_thread() if self._has_sigwinch: self._previous_winch_handler = self._loop.add_signal_handler( signal.SIGWINCH, self.app.invalidate) return self def __exit__(self, *a): # Quit UI application. if self.app.is_running: self.app.exit() # Remove WINCH handler. if self._has_sigwinch: self._loop.add_signal_handler(signal.SIGWINCH, self._previous_winch_handler) self._thread.join() def __call__(self, data=None, label='', remove_when_done=False, total=None): """ Start a new counter. :param label: Title text or description for this progress. (This can be formatted text as well). :param remove_when_done: When `True`, hide this progress bar. :param total: Specify the maximum value if it can't be calculated by calling ``len``. """ assert is_formatted_text(label) assert isinstance(remove_when_done, bool) counter = ProgressBarCounter( self, data, label=label, remove_when_done=remove_when_done, total=total) self.counters.append(counter) return counter def invalidate(self): self.app.invalidate() class _ProgressControl(UIControl): """ User control for the progress bar. """ def __init__(self, progress_bar, formatter): self.progress_bar = progress_bar self.formatter = formatter self._key_bindings = create_key_bindings() def create_content(self, width, height): items = [] for pr in self.progress_bar.counters: try: text = self.formatter.format(self.progress_bar, pr, width) except BaseException: traceback.print_exc() text = 'ERROR' items.append(to_formatted_text(text)) def get_line(i): return items[i] return UIContent( get_line=get_line, line_count=len(items), show_cursor=False) def is_focusable(self): return True # Make sure that the key bindings work. def get_key_bindings(self): return self._key_bindings class ProgressBarCounter(object): """ An individual counter (A progress bar can have multiple counters). """ def __init__(self, progress_bar, data=None, label='', remove_when_done=False, total=None): self.start_time = datetime.datetime.now() self.progress_bar = progress_bar self.data = data self.current = 0 self.label = label self.remove_when_done = remove_when_done self.done = False if total is None: try: self.total = len(data) except TypeError: self.total = None # We don't know the total length. else: self.total = total def __iter__(self): try: for item in self.data: self.current += 1 self.progress_bar.invalidate() yield item finally: self.done = True if self.remove_when_done: self.progress_bar.counters.remove(self) @property def percentage(self): if self.total is None: return 0 else: return self.current * 100 / max(self.total, 1) @property def time_elapsed(self): """ return how much time has been elapsed since the start. """ return datetime.datetime.now() - self.start_time @property def time_left(self): """ Timedelta representing the time left. """ if self.total is None or not self.percentage: return None else: return self.time_elapsed * (100 - self.percentage) / self.percentage @contextlib.contextmanager def _auto_refresh_context(app, refresh_interval=None): " Return a context manager for the auto-refresh loop. " done = [False] # nonlocal # Enter. def run(): while not done[0]: time.sleep(refresh_interval) app.invalidate() if refresh_interval: t = threading.Thread(target=run) t.daemon = True t.start() try: yield finally: # Exit. done[0] = True prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/progress_bar/formatters.py0000644000175100017510000002167413545407204030702 0ustar jonathanjonathan00000000000000""" Formatter classes for the progress bar. Each progress bar consists of a list of these formatters. """ from __future__ import unicode_literals import time from abc import ABCMeta, abstractmethod from six import text_type, with_metaclass from prompt_toolkit.formatted_text import HTML, to_formatted_text from prompt_toolkit.formatted_text.utils import fragment_list_width from prompt_toolkit.layout.dimension import D from prompt_toolkit.layout.utils import explode_text_fragments from prompt_toolkit.utils import get_cwidth __all__ = [ 'Formatter', 'Text', 'Label', 'Percentage', 'Bar', 'Progress', 'TimeElapsed', 'TimeLeft', 'IterationsPerSecond', 'SpinningWheel', 'Rainbow', 'create_default_formatters', ] class Formatter(with_metaclass(ABCMeta, object)): """ Base class for any formatter. """ @abstractmethod def format(self, progress_bar, progress, width): pass def get_width(self, progress_bar): return D() class Text(Formatter): """ Display plain text. """ def __init__(self, text, style=''): self.text = to_formatted_text(text, style=style) def format(self, progress_bar, progress, width): return self.text def get_width(self, progress_bar): return fragment_list_width(self.text) class Label(Formatter): """ Display the name of the current task. :param width: If a `width` is given, use this width. Scroll the text if it doesn't fit in this width. :param suffix: String suffix to be added after the task name, e.g. ': '. If no task name was given, no suffix will be added. """ def __init__(self, width=None, suffix=''): assert isinstance(suffix, text_type) self.width = width self.suffix = suffix def _add_suffix(self, label): label = to_formatted_text(label, style='class:label') return label + [('', self.suffix)] def format(self, progress_bar, progress, width): label = self._add_suffix(progress.label) cwidth = fragment_list_width(label) if cwidth > width: # It doesn't fit -> scroll task name. label = explode_text_fragments(label) max_scroll = cwidth - width current_scroll = int(time.time() * 3 % max_scroll) label = label[current_scroll:] return label def get_width(self, progress_bar): if self.width: return self.width all_labels = [self._add_suffix(c.label) for c in progress_bar.counters] if all_labels: max_widths = max(fragment_list_width(l) for l in all_labels) return D(preferred=max_widths, max=max_widths) else: return D() class Percentage(Formatter): """ Display the progress as a percentage. """ template = '{percentage:>5}%' def format(self, progress_bar, progress, width): return HTML(self.template).format( percentage=round(progress.percentage, 1)) def get_width(self, progress_bar): return D.exact(6) class Bar(Formatter): """ Display the progress bar itself. """ template = '{start}{bar_a}{bar_b}{bar_c}{end}' def __init__(self, start='[', end=']', sym_a='=', sym_b='>', sym_c=' ', unknown='#'): assert len(sym_a) == 1 and get_cwidth(sym_a) == 1 assert len(sym_c) == 1 and get_cwidth(sym_c) == 1 self.start = start self.end = end self.sym_a = sym_a self.sym_b = sym_b self.sym_c = sym_c self.unknown = unknown def format(self, progress_bar, progress, width): # Subtract left, bar_b and right. width -= get_cwidth(self.start + self.sym_b + self.end) if progress.total: pb_a = int(progress.percentage * width / 100) bar_a = self.sym_a * pb_a bar_b = self.sym_b bar_c = self.sym_c * (width - pb_a) else: # Total is unknown. pb_a = int(time.time() * 20 % 100 * width / 100) bar_a = self.sym_c * pb_a bar_b = self.unknown bar_c = self.sym_c * (width - pb_a) return HTML(self.template).format( start=self.start, end=self.end, bar_a=bar_a, bar_b=bar_b, bar_c=bar_c) def get_width(self, progress_bar): return D(min=9) class Progress(Formatter): """ Display the progress as text. E.g. "8/20" """ template = '{current:>3}/{total:>3}' def format(self, progress_bar, progress, width): return HTML(self.template).format( current=progress.current, total=progress.total or '?') def get_width(self, progress_bar): all_lengths = [len('{0:>3}'.format(c.total)) for c in progress_bar.counters] all_lengths.append(1) return D.exact(max(all_lengths) * 2 + 1) def _format_timedelta(timedelta): """ Return hh:mm:ss, or mm:ss if the amount of hours is zero. """ result = '{0}'.format(timedelta).split('.')[0] if result.startswith('0:'): result = result[2:] return result class TimeElapsed(Formatter): """ Display the elapsed time. """ def format(self, progress_bar, progress, width): text = _format_timedelta(progress.time_elapsed).rjust(width) return HTML('{time_elapsed}').format(time_elapsed=text) def get_width(self, progress_bar): all_values = [len(_format_timedelta(c.time_elapsed)) for c in progress_bar.counters] if all_values: return max(all_values) return 0 class TimeLeft(Formatter): """ Display the time left. """ template = '{time_left}' unknown = '?:??:??' def format(self, progress_bar, progress, width): if progress.total: time_left = _format_timedelta(progress.time_left) else: time_left = self.unknown return HTML(self.template).format(time_left=time_left.rjust(width)) def get_width(self, progress_bar): all_values = [len(_format_timedelta(c.time_left)) if c.total else 7 for c in progress_bar.counters] if all_values: return max(all_values) return 0 class IterationsPerSecond(Formatter): """ Display the iterations per second. """ template = '{iterations_per_second:.2f}' def format(self, progress_bar, progress, width): value = progress.current / progress.time_elapsed.total_seconds() return HTML(self.template.format(iterations_per_second=value)) def get_width(self, progress_bar): all_values = [len('{0:.2f}'.format(c.current / c.time_elapsed.total_seconds())) for c in progress_bar.counters] if all_values: return max(all_values) return 0 class SpinningWheel(Formatter): """ Display a spinning wheel. """ characters = r'/-\|' def format(self, progress_bar, progress, width): index = int(time.time() * 3) % len(self.characters) return HTML('{0}').format(self.characters[index]) def get_width(self, progress_bar): return D.exact(1) def _hue_to_rgb(hue): " Take hue between 0 and 1, return (r, g, b). " i = int(hue * 6.) f = (hue * 6.) - i q = int(255 * (1. - f)) t = int(255 * (1. - (1. - f))) i %= 6 return [ (255, t, 0), (q, 255, 0), (0, 255, t), (0, q, 255), (t, 0, 255), (255, 0, q), ][i] class Rainbow(Formatter): """ For the fun. Add rainbow colors to any of the other formatters. """ colors = ['#%.2x%.2x%.2x' % _hue_to_rgb(h / 100.) for h in range(0, 100)] def __init__(self, formatter): self.formatter = formatter def format(self, progress_bar, progress, width): # Get formatted text from nested formatter, and explode it in # text/style tuples. result = self.formatter.format(progress_bar, progress, width) result = explode_text_fragments(to_formatted_text(result)) # Insert colors. result2 = [] shift = int(time.time() * 3) % len(self.colors) for i, (style, text) in enumerate(result): result2.append((style + ' ' + self.colors[(i + shift) % len(self.colors)], text)) return result2 def get_width(self, progress_bar): return self.formatter.get_width(progress_bar) def create_default_formatters(): """ Return the list of default formatters. """ return [ Label(), Text(' '), Percentage(), Text(' '), Bar(), Text(' '), Progress(), Text(' '), Text('eta [', style='class:time-left'), TimeLeft(), Text(']', style='class:time-left'), Text(' '), ] prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/prompt.py0000644000175100017510000010655213545407204025344 0ustar jonathanjonathan00000000000000""" Line editing functionality. --------------------------- This provides a UI for a line input, similar to GNU Readline, libedit and linenoise. Either call the `prompt` function for every line input. Or create an instance of the :class:`.PromptSession` class and call the `prompt` method from that class. In the second case, we'll have a 'session' that keeps all the state like the history in between several calls. There is a lot of overlap between the arguments taken by the `prompt` function and the `PromptSession` (like `completer`, `style`, etcetera). There we have the freedom to decide which settings we want for the whole 'session', and which we want for an individual `prompt`. Example:: # Simple `prompt` call. result = prompt('Say something: ') # Using a 'session'. s = PromptSession() result = s.prompt('Say something: ') """ from __future__ import unicode_literals import contextlib import threading import time from functools import partial from six import text_type from prompt_toolkit.application import Application from prompt_toolkit.application.current import get_app from prompt_toolkit.auto_suggest import DynamicAutoSuggest from prompt_toolkit.buffer import Buffer from prompt_toolkit.clipboard import DynamicClipboard, InMemoryClipboard from prompt_toolkit.completion import DynamicCompleter, ThreadedCompleter from prompt_toolkit.document import Document from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER, EditingMode from prompt_toolkit.eventloop import ( From, Return, ensure_future, get_event_loop, ) from prompt_toolkit.filters import ( Condition, has_arg, has_focus, is_done, is_true, renderer_height_is_known, to_filter, ) from prompt_toolkit.formatted_text import ( fragment_list_to_text, merge_formatted_text, to_formatted_text, ) from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.input.defaults import get_default_input from prompt_toolkit.key_binding.bindings.auto_suggest import ( load_auto_suggest_bindings, ) from prompt_toolkit.key_binding.bindings.completion import ( display_completions_like_readline, ) from prompt_toolkit.key_binding.bindings.open_in_editor import ( load_open_in_editor_bindings, ) from prompt_toolkit.key_binding.key_bindings import ( ConditionalKeyBindings, DynamicKeyBindings, KeyBindings, KeyBindingsBase, merge_key_bindings, ) from prompt_toolkit.keys import Keys from prompt_toolkit.layout import Float, FloatContainer, HSplit, Window from prompt_toolkit.layout.containers import ConditionalContainer, WindowAlign from prompt_toolkit.layout.controls import ( BufferControl, FormattedTextControl, SearchBufferControl, ) from prompt_toolkit.layout.dimension import Dimension from prompt_toolkit.layout.layout import Layout from prompt_toolkit.layout.menus import ( CompletionsMenu, MultiColumnCompletionsMenu, ) from prompt_toolkit.layout.processors import ( AppendAutoSuggestion, ConditionalProcessor, DisplayMultipleCursors, DynamicProcessor, HighlightIncrementalSearchProcessor, HighlightSelectionProcessor, PasswordProcessor, ReverseSearchProcessor, merge_processors, ) from prompt_toolkit.layout.utils import explode_text_fragments from prompt_toolkit.lexers import DynamicLexer from prompt_toolkit.output.defaults import get_default_output from prompt_toolkit.styles import ( BaseStyle, ConditionalStyleTransformation, DynamicStyle, DynamicStyleTransformation, StyleTransformation, SwapLightAndDarkStyleTransformation, merge_style_transformations, ) from prompt_toolkit.utils import get_cwidth, suspend_to_background_supported from prompt_toolkit.validation import DynamicValidator from prompt_toolkit.widgets.toolbars import ( SearchToolbar, SystemToolbar, ValidationToolbar, ) __all__ = [ 'PromptSession', 'prompt', 'confirm', 'create_confirm_session', # Used by '_display_completions_like_readline'. 'CompleteStyle', ] def _split_multiline_prompt(get_prompt_text): """ Take a `get_prompt_text` function and return three new functions instead. One that tells whether this prompt consists of multiple lines; one that returns the fragments to be shown on the lines above the input; and another one with the fragments to be shown at the first line of the input. """ def has_before_fragments(): for fragment, char in get_prompt_text(): if '\n' in char: return True return False def before(): result = [] found_nl = False for fragment, char in reversed(explode_text_fragments(get_prompt_text())): if found_nl: result.insert(0, (fragment, char)) elif char == '\n': found_nl = True return result def first_input_line(): result = [] for fragment, char in reversed(explode_text_fragments(get_prompt_text())): if char == '\n': break else: result.insert(0, (fragment, char)) return result return has_before_fragments, before, first_input_line class _RPrompt(Window): " The prompt that is displayed on the right side of the Window. " def __init__(self, get_formatted_text): super(_RPrompt, self).__init__( FormattedTextControl(get_formatted_text), align=WindowAlign.RIGHT, style='class:rprompt') class CompleteStyle: " How to display autocompletions for the prompt. " COLUMN = 'COLUMN' MULTI_COLUMN = 'MULTI_COLUMN' READLINE_LIKE = 'READLINE_LIKE' class PromptSession(object): """ PromptSession for a prompt application, which can be used as a GNU Readline replacement. This is a wrapper around a lot of ``prompt_toolkit`` functionality and can be a replacement for `raw_input`. All parameters that expect "formatted text" can take either just plain text (a unicode object), a list of ``(style_str, text)`` tuples or an HTML object. Example usage:: s = PromptSession(message='>') text = s.prompt() :param message: Plain text or formatted text to be shown before the prompt. This can also be a callable that returns formatted text. :param multiline: `bool` or :class:`~prompt_toolkit.filters.Filter`. When True, prefer a layout that is more adapted for multiline input. Text after newlines is automatically indented, and search/arg input is shown below the input, instead of replacing the prompt. :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.Filter`. When True (the default), automatically wrap long lines instead of scrolling horizontally. :param is_password: Show asterisks instead of the actual typed characters. :param editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``. :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``. :param complete_while_typing: `bool` or :class:`~prompt_toolkit.filters.Filter`. Enable autocompletion while typing. :param validate_while_typing: `bool` or :class:`~prompt_toolkit.filters.Filter`. Enable input validation while typing. :param enable_history_search: `bool` or :class:`~prompt_toolkit.filters.Filter`. Enable up-arrow parting string matching. :param search_ignore_case: :class:`~prompt_toolkit.filters.Filter`. Search case insensitive. :param lexer: :class:`~prompt_toolkit.lexers.Lexer` to be used for the syntax highlighting. :param validator: :class:`~prompt_toolkit.validation.Validator` instance for input validation. :param completer: :class:`~prompt_toolkit.completion.Completer` instance for input completion. :param complete_in_thread: `bool` or :class:`~prompt_toolkit.filters.Filter`. Run the completer code in a background thread in order to avoid blocking the user interface. For ``CompleteStyle.READLINE_LIKE``, this setting has no effect. There we always run the completions in the main thread. :param reserve_space_for_menu: Space to be reserved for displaying the menu. (0 means that no space needs to be reserved.) :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` instance for input suggestions. :param style: :class:`.Style` instance for the color scheme. :param include_default_pygments_style: `bool` or :class:`~prompt_toolkit.filters.Filter`. Tell whether the default styling for Pygments lexers has to be included. By default, this is true, but it is recommended to be disabled if another Pygments style is passed as the `style` argument, otherwise, two Pygments styles will be merged. :param style_transformation: :class:`~prompt_toolkit.style.StyleTransformation` instance. :param swap_light_and_dark_colors: `bool` or :class:`~prompt_toolkit.filters.Filter`. When enabled, apply :class:`~prompt_toolkit.style.SwapLightAndDarkStyleTransformation`. This is useful for switching between dark and light terminal backgrounds. :param enable_system_prompt: `bool` or :class:`~prompt_toolkit.filters.Filter`. Pressing Meta+'!' will show a system prompt. :param enable_suspend: `bool` or :class:`~prompt_toolkit.filters.Filter`. Enable Control-Z style suspension. :param enable_open_in_editor: `bool` or :class:`~prompt_toolkit.filters.Filter`. Pressing 'v' in Vi mode or C-X C-E in emacs mode will open an external editor. :param history: :class:`~prompt_toolkit.history.History` instance. :param clipboard: :class:`~prompt_toolkit.clipboard.Clipboard` instance. (e.g. :class:`~prompt_toolkit.clipboard.InMemoryClipboard`) :param rprompt: Text or formatted text to be displayed on the right side. This can also be a callable that returns (formatted) text. :param bottom_toolbar: Formatted text or callable which is supposed to return formatted text. :param prompt_continuation: Text that needs to be displayed for a multiline prompt continuation. This can either be formatted text or a callable that takes a `prompt_width`, `line_number` and `wrap_count` as input and returns formatted text. :param complete_style: ``CompleteStyle.COLUMN``, ``CompleteStyle.MULTI_COLUMN`` or ``CompleteStyle.READLINE_LIKE``. :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.Filter` to enable mouse support. :param refresh_interval: (number; in seconds) When given, refresh the UI every so many seconds. :param inputhook: None or an Inputhook callable that takes an `InputHookContext` object. """ _fields = ( 'message', 'lexer', 'completer', 'complete_in_thread', 'is_password', 'editing_mode', 'key_bindings', 'is_password', 'bottom_toolbar', 'style', 'style_transformation', 'swap_light_and_dark_colors', 'color_depth', 'include_default_pygments_style', 'rprompt', 'multiline', 'prompt_continuation', 'wrap_lines', 'enable_history_search', 'search_ignore_case', 'complete_while_typing', 'validate_while_typing', 'complete_style', 'mouse_support', 'auto_suggest', 'clipboard', 'validator', 'refresh_interval', 'input_processors', 'enable_system_prompt', 'enable_suspend', 'enable_open_in_editor', 'reserve_space_for_menu', 'tempfile_suffix', 'inputhook') def __init__( self, message='', multiline=False, wrap_lines=True, is_password=False, vi_mode=False, editing_mode=EditingMode.EMACS, complete_while_typing=True, validate_while_typing=True, enable_history_search=False, search_ignore_case=False, lexer=None, enable_system_prompt=False, enable_suspend=False, enable_open_in_editor=False, validator=None, completer=None, complete_in_thread=False, reserve_space_for_menu=8, complete_style=None, auto_suggest=None, style=None, style_transformation=None, swap_light_and_dark_colors=False, color_depth=None, include_default_pygments_style=True, history=None, clipboard=None, prompt_continuation=None, rprompt=None, bottom_toolbar=None, mouse_support=False, input_processors=None, key_bindings=None, erase_when_done=False, tempfile_suffix='.txt', inputhook=None, refresh_interval=0, input=None, output=None): assert style is None or isinstance(style, BaseStyle) assert style_transformation is None or isinstance(style_transformation, StyleTransformation) assert input_processors is None or isinstance(input_processors, list) assert key_bindings is None or isinstance(key_bindings, KeyBindingsBase) # Defaults. output = output or get_default_output() input = input or get_default_input() history = history or InMemoryHistory() clipboard = clipboard or InMemoryClipboard() # Ensure backwards-compatibility, when `vi_mode` is passed. if vi_mode: editing_mode = EditingMode.VI # Store all settings in this class. self.input = input self.output = output # Store all settings in this class. for name in self._fields: if name not in ('editing_mode', ): value = locals()[name] setattr(self, name, value) # Create buffers, layout and Application. self.history = history self.default_buffer = self._create_default_buffer() self.search_buffer = self._create_search_buffer() self.layout = self._create_layout() self.app = self._create_application(editing_mode, erase_when_done) def _dyncond(self, attr_name): """ Dynamically take this setting from this 'PromptSession' class. `attr_name` represents an attribute name of this class. Its value can either be a boolean or a `Filter`. This returns something that can be used as either a `Filter` or `Filter`. """ @Condition def dynamic(): value = getattr(self, attr_name) return to_filter(value)() return dynamic def _create_default_buffer(self): """ Create and return the default input buffer. """ dyncond = self._dyncond # Create buffers list. def accept(buff): """ Accept the content of the default buffer. This is called when the validation succeeds. """ self.app.exit(result=buff.document.text) return True # Keep text, we call 'reset' later on. return Buffer( name=DEFAULT_BUFFER, # Make sure that complete_while_typing is disabled when # enable_history_search is enabled. (First convert to Filter, # to avoid doing bitwise operations on bool objects.) complete_while_typing=Condition(lambda: is_true(self.complete_while_typing) and not is_true(self.enable_history_search) and not self.complete_style == CompleteStyle.READLINE_LIKE), validate_while_typing=dyncond('validate_while_typing'), enable_history_search=dyncond('enable_history_search'), validator=DynamicValidator(lambda: self.validator), completer=DynamicCompleter(lambda: ThreadedCompleter(self.completer) if self.complete_in_thread and self.completer else self.completer), history=self.history, auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), accept_handler=accept, tempfile_suffix=lambda: self.tempfile_suffix) def _create_search_buffer(self): return Buffer(name=SEARCH_BUFFER) def _create_layout(self): """ Create `Layout` for this prompt. """ dyncond = self._dyncond # Create functions that will dynamically split the prompt. (If we have # a multiline prompt.) has_before_fragments, get_prompt_text_1, get_prompt_text_2 = \ _split_multiline_prompt(self._get_prompt) default_buffer = self.default_buffer search_buffer = self.search_buffer # Create processors list. all_input_processors = [ HighlightIncrementalSearchProcessor(), HighlightSelectionProcessor(), ConditionalProcessor(AppendAutoSuggestion(), has_focus(default_buffer) & ~is_done), ConditionalProcessor(PasswordProcessor(), dyncond('is_password')), DisplayMultipleCursors(), # Users can insert processors here. DynamicProcessor(lambda: merge_processors(self.input_processors or [])), ] # Create bottom toolbars. bottom_toolbar = ConditionalContainer( Window(FormattedTextControl( lambda: self.bottom_toolbar, style='class:bottom-toolbar.text'), style='class:bottom-toolbar', dont_extend_height=True, height=Dimension(min=1)), filter=~is_done & renderer_height_is_known & Condition(lambda: self.bottom_toolbar is not None)) search_toolbar = SearchToolbar( search_buffer, ignore_case=dyncond('search_ignore_case')) search_buffer_control = SearchBufferControl( buffer=search_buffer, input_processors=[ ReverseSearchProcessor(), ], ignore_case=dyncond('search_ignore_case')) system_toolbar = SystemToolbar( enable_global_bindings=dyncond('enable_system_prompt')) def get_search_buffer_control(): " Return the UIControl to be focused when searching start. " if is_true(self.multiline): return search_toolbar.control else: return search_buffer_control default_buffer_control = BufferControl( buffer=default_buffer, search_buffer_control=get_search_buffer_control, input_processors=all_input_processors, include_default_input_processors=False, lexer=DynamicLexer(lambda: self.lexer), preview_search=True) default_buffer_window = Window( default_buffer_control, height=self._get_default_buffer_control_height, get_line_prefix=partial( self._get_line_prefix, get_prompt_text_2=get_prompt_text_2), wrap_lines=dyncond('wrap_lines')) @Condition def multi_column_complete_style(): return self.complete_style == CompleteStyle.MULTI_COLUMN # Build the layout. layout = HSplit([ # The main input, with completion menus floating on top of it. FloatContainer( HSplit([ ConditionalContainer( Window( FormattedTextControl(get_prompt_text_1), dont_extend_height=True), Condition(has_before_fragments) ), ConditionalContainer( default_buffer_window, Condition(lambda: get_app().layout.current_control != search_buffer_control), ), ConditionalContainer( Window(search_buffer_control), Condition(lambda: get_app().layout.current_control == search_buffer_control), ), ]), [ # Completion menus. # NOTE: Especially the multi-column menu needs to be # transparent, because the shape is not always # rectangular due to the meta-text below the menu. Float(xcursor=True, ycursor=True, transparent=True, content=CompletionsMenu( max_height=16, scroll_offset=1, extra_filter=has_focus(default_buffer) & ~multi_column_complete_style)), Float(xcursor=True, ycursor=True, transparent=True, content=MultiColumnCompletionsMenu( show_meta=True, extra_filter=has_focus(default_buffer) & multi_column_complete_style)), # The right prompt. Float(right=0, top=0, hide_when_covering_content=True, content=_RPrompt(lambda: self.rprompt)), ] ), ConditionalContainer( ValidationToolbar(), filter=~is_done), ConditionalContainer( system_toolbar, dyncond('enable_system_prompt') & ~is_done), # In multiline mode, we use two toolbars for 'arg' and 'search'. ConditionalContainer( Window(FormattedTextControl(self._get_arg_text), height=1), dyncond('multiline') & has_arg), ConditionalContainer(search_toolbar, dyncond('multiline') & ~is_done), bottom_toolbar, ]) return Layout(layout, default_buffer_window) def _create_application(self, editing_mode, erase_when_done): """ Create the `Application` object. """ dyncond = self._dyncond # Default key bindings. auto_suggest_bindings = load_auto_suggest_bindings() open_in_editor_bindings = load_open_in_editor_bindings() prompt_bindings = self._create_prompt_bindings() # Create application application = Application( layout=self.layout, style=DynamicStyle(lambda: self.style), style_transformation=merge_style_transformations([ DynamicStyleTransformation(lambda: self.style_transformation), ConditionalStyleTransformation( SwapLightAndDarkStyleTransformation(), dyncond('swap_light_and_dark_colors'), ), ]), include_default_pygments_style=dyncond('include_default_pygments_style'), clipboard=DynamicClipboard(lambda: self.clipboard), key_bindings=merge_key_bindings([ merge_key_bindings([ auto_suggest_bindings, ConditionalKeyBindings(open_in_editor_bindings, dyncond('enable_open_in_editor') & has_focus(DEFAULT_BUFFER)), prompt_bindings ]), DynamicKeyBindings(lambda: self.key_bindings), ]), mouse_support=dyncond('mouse_support'), editing_mode=editing_mode, erase_when_done=erase_when_done, reverse_vi_search_direction=True, color_depth=lambda: self.color_depth, # I/O. input=self.input, output=self.output) # During render time, make sure that we focus the right search control # (if we are searching). - This could be useful if people make the # 'multiline' property dynamic. ''' def on_render(app): multiline = is_true(self.multiline) current_control = app.layout.current_control if multiline: if current_control == search_buffer_control: app.layout.current_control = search_toolbar.control app.invalidate() else: if current_control == search_toolbar.control: app.layout.current_control = search_buffer_control app.invalidate() app.on_render += on_render ''' return application def _create_prompt_bindings(self): """ Create the KeyBindings for a prompt application. """ kb = KeyBindings() handle = kb.add default_focused = has_focus(DEFAULT_BUFFER) @Condition def do_accept(): return (not is_true(self.multiline) and self.app.layout.has_focus(DEFAULT_BUFFER)) @handle('enter', filter=do_accept & default_focused) def _(event): " Accept input when enter has been pressed. " self.default_buffer.validate_and_handle() @Condition def readline_complete_style(): return self.complete_style == CompleteStyle.READLINE_LIKE @handle('tab', filter=readline_complete_style & default_focused) def _(event): " Display completions (like Readline). " display_completions_like_readline(event) @handle('c-c', filter=default_focused) def _(event): " Abort when Control-C has been pressed. " event.app.exit(exception=KeyboardInterrupt, style='class:aborting') @Condition def ctrl_d_condition(): """ Ctrl-D binding is only active when the default buffer is selected and empty. """ app = get_app() return (app.current_buffer.name == DEFAULT_BUFFER and not app.current_buffer.text) @handle('c-d', filter=ctrl_d_condition & default_focused) def _(event): " Exit when Control-D has been pressed. " event.app.exit(exception=EOFError, style='class:exiting') suspend_supported = Condition(suspend_to_background_supported) @Condition def enable_suspend(): return to_filter(self.enable_suspend)() @handle('c-z', filter=suspend_supported & enable_suspend) def _(event): """ Suspend process to background. """ event.app.suspend_to_background() return kb @contextlib.contextmanager def _auto_refresh_context(self): " Return a context manager for the auto-refresh loop. " done = [False] # nonlocal # Enter. def run(): while not done[0]: time.sleep(self.refresh_interval) self.app.invalidate() if self.refresh_interval: t = threading.Thread(target=run) t.daemon = True t.start() try: yield finally: # Exit. done[0] = True def prompt( self, # When any of these arguments are passed, this value is overwritten # in this PromptSession. message=None, # `message` should go first, because people call it # as positional argument. editing_mode=None, refresh_interval=None, vi_mode=None, lexer=None, completer=None, complete_in_thread=None, is_password=None, key_bindings=None, bottom_toolbar=None, style=None, color_depth=None, include_default_pygments_style=None, style_transformation=None, swap_light_and_dark_colors=None, rprompt=None, multiline=None, prompt_continuation=None, wrap_lines=None, enable_history_search=None, search_ignore_case=None, complete_while_typing=None, validate_while_typing=None, complete_style=None, auto_suggest=None, validator=None, clipboard=None, mouse_support=None, input_processors=None, reserve_space_for_menu=None, enable_system_prompt=None, enable_suspend=None, enable_open_in_editor=None, tempfile_suffix=None, inputhook=None, # Following arguments are specific to the current `prompt()` call. async_=False, default='', accept_default=False, pre_run=None): """ Display the prompt. All the arguments are a subset of the :class:`~.PromptSession` class itself. This will raise ``KeyboardInterrupt`` when control-c has been pressed (for abort) and ``EOFError`` when control-d has been pressed (for exit). Additional arguments, specific for this prompt: :param async_: When `True` return a `Future` instead of waiting for the prompt to finish. :param default: The default input text to be shown. (This can be edited by the user). :param accept_default: When `True`, automatically accept the default value without allowing the user to edit the input. :param pre_run: Callable, called at the start of `Application.run`. """ assert isinstance(default, text_type) # NOTE: We used to create a backup of the PromptSession attributes and # restore them after exiting the prompt. This code has been # removed, because it was confusing and didn't really serve a use # case. (People were changing `Application.editing_mode` # dynamically and surprised that it was reset after every call.) # Take settings from 'prompt'-arguments. for name in self._fields: value = locals()[name] if value is not None: setattr(self, name, value) if vi_mode: self.editing_mode = EditingMode.VI def pre_run2(): if pre_run: pre_run() if accept_default: # Validate and handle input. We use `call_from_executor` in # order to run it "soon" (during the next iteration of the # event loop), instead of right now. Otherwise, it won't # display the default value. get_event_loop().call_from_executor( self.default_buffer.validate_and_handle) def run_sync(): with self._auto_refresh_context(): self.default_buffer.reset(Document(default)) return self.app.run(inputhook=self.inputhook, pre_run=pre_run2) def run_async(): with self._auto_refresh_context(): self.default_buffer.reset(Document(default)) result = yield From(self.app.run_async(pre_run=pre_run2)) raise Return(result) if async_: return ensure_future(run_async()) else: return run_sync() @property def editing_mode(self): return self.app.editing_mode @editing_mode.setter def editing_mode(self, value): self.app.editing_mode = value def _get_default_buffer_control_height(self): # If there is an autocompletion menu to be shown, make sure that our # layout has at least a minimal height in order to display it. if (self.completer is not None and self.complete_style != CompleteStyle.READLINE_LIKE): space = self.reserve_space_for_menu else: space = 0 if space and not get_app().is_done: buff = self.default_buffer # Reserve the space, either when there are completions, or when # `complete_while_typing` is true and we expect completions very # soon. if buff.complete_while_typing() or buff.complete_state is not None: return Dimension(min=space) return Dimension() def _get_prompt(self): return to_formatted_text(self.message, style='class:prompt') def _get_continuation(self, width, line_number, wrap_count): """ Insert the prompt continuation. :param width: The width that was used for the prompt. (more or less can be used.) :param line_number: :param wrap_count: Amount of times that the line has been wrapped. """ prompt_continuation = self.prompt_continuation if callable(prompt_continuation): prompt_continuation = prompt_continuation(width, line_number, wrap_count) # When the continuation prompt is not given, choose the same width as # the actual prompt. if not prompt_continuation and is_true(self.multiline): prompt_continuation = ' ' * width return to_formatted_text( prompt_continuation, style='class:prompt-continuation') def _get_line_prefix(self, line_number, wrap_count, get_prompt_text_2): """ Return whatever needs to be inserted before every line. (the prompt, or a line continuation.) """ # First line: display the "arg" or the prompt. if line_number == 0 and wrap_count == 0: if not is_true(self.multiline) and get_app().key_processor.arg is not None: return self._inline_arg() else: return get_prompt_text_2() # For the next lines, display the appropriate continuation. prompt_width = get_cwidth(fragment_list_to_text(get_prompt_text_2())) return self._get_continuation(prompt_width, line_number, wrap_count) def _get_arg_text(self): " 'arg' toolbar, for in multiline mode. " arg = self.app.key_processor.arg if arg == '-': arg = '-1' return [ ('class:arg-toolbar', 'Repeat: '), ('class:arg-toolbar.text', arg) ] def _inline_arg(self): " 'arg' prefix, for in single line mode. " app = get_app() if app.key_processor.arg is None: return [] else: arg = app.key_processor.arg return [ ('class:prompt.arg', '(arg: '), ('class:prompt.arg.text', str(arg)), ('class:prompt.arg', ') '), ] def prompt(*a, **kw): """ The global `prompt` function. This will create a new `PromptSession` instance for every call. """ # Input and output arguments have to be passed to the 'PromptSession' # class, not its method. input = kw.pop('input', None) output = kw.pop('output', None) history = kw.pop('history', None) session = PromptSession(input=input, output=output, history=history) return session.prompt(*a, **kw) prompt.__doc__ = PromptSession.prompt.__doc__ def create_confirm_session(message, suffix=' (y/n) '): """ Create a `PromptSession` object for the 'confirm' function. """ assert isinstance(message, text_type) bindings = KeyBindings() @bindings.add('y') @bindings.add('Y') def yes(event): session.default_buffer.text = 'y' event.app.exit(result=True) @bindings.add('n') @bindings.add('N') def no(event): session.default_buffer.text = 'n' event.app.exit(result=False) @bindings.add(Keys.Any) def _(event): " Disallow inserting other text. " pass complete_message = merge_formatted_text([message, suffix]) session = PromptSession(complete_message, key_bindings=bindings) return session def confirm(message='Confirm?', suffix=' (y/n) '): """ Display a confirmation prompt that returns True/False. """ session = create_confirm_session(message, suffix) return session.prompt() prompt_toolkit-2.0.10/prompt_toolkit/shortcuts/utils.py0000644000175100017510000001306113545407204025153 0ustar jonathanjonathan00000000000000from __future__ import print_function, unicode_literals import six from prompt_toolkit.application import Application from prompt_toolkit.eventloop import get_event_loop from prompt_toolkit.formatted_text import FormattedText, to_formatted_text from prompt_toolkit.input import DummyInput from prompt_toolkit.layout import Layout from prompt_toolkit.output import ColorDepth, Output from prompt_toolkit.output.defaults import create_output, get_default_output from prompt_toolkit.renderer import \ print_formatted_text as renderer_print_formatted_text from prompt_toolkit.styles import ( BaseStyle, default_pygments_style, default_ui_style, merge_styles, ) __all__ = [ 'print_formatted_text', 'print_container', 'clear', 'set_title', 'clear_title', ] def print_formatted_text(*values, **kwargs): """ :: print_formatted_text(*values, sep=' ', end='\\n', file=None, flush=False, style=None, output=None) Print text to stdout. This is supposed to be compatible with Python's print function, but supports printing of formatted text. You can pass a :class:`~prompt_toolkit.formatted_text.FormattedText`, :class:`~prompt_toolkit.formatted_text.HTML` or :class:`~prompt_toolkit.formatted_text.ANSI` object to print formatted text. * Print HTML as follows:: print_formatted_text(HTML('Some italic text This is red!')) style = Style.from_dict({ 'hello': '#ff0066', 'world': '#884444 italic', }) print_formatted_text(HTML('Hello world!'), style=style) * Print a list of (style_str, text) tuples in the given style to the output. E.g.:: style = Style.from_dict({ 'hello': '#ff0066', 'world': '#884444 italic', }) fragments = FormattedText([ ('class:hello', 'Hello'), ('class:world', 'World'), ]) print_formatted_text(fragments, style=style) If you want to print a list of Pygments tokens, wrap it in :class:`~prompt_toolkit.formatted_text.PygmentsTokens` to do the conversion. :param values: Any kind of printable object, or formatted string. :param sep: String inserted between values, default a space. :param end: String appended after the last value, default a newline. :param style: :class:`.Style` instance for the color scheme. :param include_default_pygments_style: `bool`. Include the default Pygments style when set to `True` (the default). """ # Pop kwargs (Python 2 compatibility). sep = kwargs.pop('sep', ' ') end = kwargs.pop('end', '\n') file = kwargs.pop('file', None) flush = kwargs.pop('flush', False) style = kwargs.pop('style', None) output = kwargs.pop('output', None) color_depth = kwargs.pop('color_depth', None) style_transformation = kwargs.pop('style_transformation', None) include_default_pygments_style = kwargs.pop('include_default_pygments_style', True) assert not kwargs assert not (output and file) assert style is None or isinstance(style, BaseStyle) # Build/merge style. styles = [default_ui_style()] if include_default_pygments_style: styles.append(default_pygments_style()) if style: styles.append(style) merged_style = merge_styles(styles) # Create Output object. if output is None: if file: output = create_output(stdout=file) else: output = get_default_output() assert isinstance(output, Output) # Get color depth. color_depth = color_depth or ColorDepth.default() # Merges values. def to_text(val): # Normal lists which are not instances of `FormattedText` are # considered plain text. if isinstance(val, list) and not isinstance(val, FormattedText): return to_formatted_text('{0}'.format(val)) return to_formatted_text(val, auto_convert=True) fragments = [] for i, value in enumerate(values): fragments.extend(to_text(value)) if sep and i != len(values) - 1: fragments.extend(to_text(sep)) fragments.extend(to_text(end)) # Print output. renderer_print_formatted_text( output, fragments, merged_style, color_depth=color_depth, style_transformation=style_transformation) # Flush the output stream. if flush: output.flush() def print_container(container, file=None): """ Print any layout to the output in a non-interactive way. Example usage:: from prompt_toolkit.widgets import Frame, TextArea print_container( Frame(TextArea(text='Hello world!'))) """ if file: output = create_output(stdout=file) else: output = get_default_output() def exit_immediately(): # Use `call_from_executor` to exit "soon", so that we still render one # initial time, before exiting the application. get_event_loop().call_from_executor( lambda: app.exit()) app = Application( layout=Layout(container=container), output=output, input=DummyInput()) app.run(pre_run=exit_immediately) def clear(): """ Clear the screen. """ out = get_default_output() out.erase_screen() out.cursor_goto(0, 0) out.flush() def set_title(text): """ Set the terminal title. """ assert isinstance(text, six.text_type) output = get_default_output() output.set_title(text) def clear_title(): """ Erase the current title. """ set_title('') prompt_toolkit-2.0.10/prompt_toolkit/styles/0000755000175100017510000000000013545410361022723 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/styles/__init__.py0000644000175100017510000000320213545407204025033 0ustar jonathanjonathan00000000000000""" Styling for prompt_toolkit applications. """ from __future__ import absolute_import, unicode_literals from .base import ( ANSI_COLOR_NAMES, DEFAULT_ATTRS, Attrs, BaseStyle, DummyStyle, DynamicStyle, ) from .defaults import default_pygments_style, default_ui_style from .named_colors import NAMED_COLORS from .pygments import ( pygments_token_to_classname, style_from_pygments_cls, style_from_pygments_dict, ) from .style import Priority, Style, merge_styles, parse_color from .style_transformation import ( AdjustBrightnessStyleTransformation, ConditionalStyleTransformation, DummyStyleTransformation, DynamicStyleTransformation, ReverseStyleTransformation, SetDefaultColorStyleTransformation, StyleTransformation, SwapLightAndDarkStyleTransformation, merge_style_transformations, ) __all__ = [ # Base. 'Attrs', 'DEFAULT_ATTRS', 'ANSI_COLOR_NAMES', 'BaseStyle', 'DummyStyle', 'DynamicStyle', # Defaults. 'default_ui_style', 'default_pygments_style', # Style. 'Style', 'Priority', 'merge_styles', 'parse_color', # Style transformation. 'StyleTransformation', 'SwapLightAndDarkStyleTransformation', 'ReverseStyleTransformation', 'SetDefaultColorStyleTransformation', 'AdjustBrightnessStyleTransformation', 'DummyStyleTransformation', 'ConditionalStyleTransformation', 'DynamicStyleTransformation', 'merge_style_transformations', # Pygments. 'style_from_pygments_cls', 'style_from_pygments_dict', 'pygments_token_to_classname', # Named colors. 'NAMED_COLORS', ] prompt_toolkit-2.0.10/prompt_toolkit/styles/base.py0000644000175100017510000001074513545407204024220 0ustar jonathanjonathan00000000000000""" The base classes for the styling. """ from __future__ import absolute_import, unicode_literals from abc import ABCMeta, abstractmethod, abstractproperty from collections import namedtuple from six import with_metaclass __all__ = [ 'Attrs', 'DEFAULT_ATTRS', 'ANSI_COLOR_NAMES', 'ANSI_COLOR_NAMES_ALIASES', 'BaseStyle', 'DummyStyle', 'DynamicStyle', ] #: Style attributes. Attrs = namedtuple('Attrs', 'color bgcolor bold underline italic blink reverse hidden') """ :param color: Hexadecimal string. E.g. '000000' or Ansi color name: e.g. 'ansiblue' :param bgcolor: Hexadecimal string. E.g. 'ffffff' or Ansi color name: e.g. 'ansired' :param bold: Boolean :param underline: Boolean :param italic: Boolean :param blink: Boolean :param reverse: Boolean :param hidden: Boolean """ #: The default `Attrs`. DEFAULT_ATTRS = Attrs(color='', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) #: ``Attrs.bgcolor/fgcolor`` can be in either 'ffffff' format, or can be any of #: the following in case we want to take colors from the 8/16 color palette. #: Usually, in that case, the terminal application allows to configure the RGB #: values for these names. #: ISO 6429 colors ANSI_COLOR_NAMES = [ 'ansidefault', # Low intensity, dark. (One or two components 0x80, the other 0x00.) 'ansiblack', 'ansired', 'ansigreen', 'ansiyellow', 'ansiblue', 'ansimagenta', 'ansicyan', 'ansigray', # High intensity, bright. (One or two components 0xff, the other 0x00. Not supported everywhere.) 'ansibrightblack', 'ansibrightred', 'ansibrightgreen', 'ansibrightyellow', 'ansibrightblue', 'ansibrightmagenta', 'ansibrightcyan', 'ansiwhite', ] # People don't use the same ANSI color names everywhere. In prompt_toolkit 1.0 # we used some unconvential names (which were contributed like that to # Pygments). This is fixed now, but we still support the old names. # The table below maps the old aliases to the current names. ANSI_COLOR_NAMES_ALIASES = { 'ansidarkgray': 'ansibrightblack', 'ansiteal': 'ansicyan', 'ansiturquoise': 'ansibrightcyan', 'ansibrown': 'ansiyellow', 'ansipurple': 'ansimagenta', 'ansifuchsia': 'ansibrightmagenta', 'ansilightgray': 'ansigray', 'ansidarkred': 'ansired', 'ansidarkgreen': 'ansigreen', 'ansidarkblue': 'ansiblue', } assert set(ANSI_COLOR_NAMES_ALIASES.values()).issubset(set(ANSI_COLOR_NAMES)) assert not (set(ANSI_COLOR_NAMES_ALIASES.keys()) & set(ANSI_COLOR_NAMES)) class BaseStyle(with_metaclass(ABCMeta, object)): """ Abstract base class for prompt_toolkit styles. """ @abstractmethod def get_attrs_for_style_str(self, style_str, default=DEFAULT_ATTRS): """ Return :class:`.Attrs` for the given style string. :param style_str: The style string. This can contain inline styling as well as classnames (e.g. "class:title"). :param default: `Attrs` to be used if no styling was defined. """ @abstractproperty def style_rules(self): """ The list of style rules, used to create this style. (Required for `DynamicStyle` and `_MergedStyle` to work.) """ return [] @abstractmethod def invalidation_hash(self): """ Invalidation hash for the style. When this changes over time, the renderer knows that something in the style changed, and that everything has to be redrawn. """ class DummyStyle(BaseStyle): """ A style that doesn't style anything. """ def get_attrs_for_style_str(self, style_str, default=DEFAULT_ATTRS): return default def invalidation_hash(self): return 1 # Always the same value. @property def style_rules(self): return [] class DynamicStyle(BaseStyle): """ Style class that can dynamically returns an other Style. :param get_style: Callable that returns a :class:`.Style` instance. """ def __init__(self, get_style): self.get_style = get_style self._dummy = DummyStyle() def get_attrs_for_style_str(self, style_str, default=DEFAULT_ATTRS): style = self.get_style() or self._dummy assert isinstance(style, BaseStyle) return style.get_attrs_for_style_str(style_str, default) def invalidation_hash(self): return (self.get_style() or self._dummy).invalidation_hash() @property def style_rules(self): return (self.get_style() or self._dummy).style_rules prompt_toolkit-2.0.10/prompt_toolkit/styles/defaults.py0000644000175100017510000002536313545407204025117 0ustar jonathanjonathan00000000000000""" The default styling. """ from __future__ import absolute_import, unicode_literals from prompt_toolkit.cache import memoized from .base import ANSI_COLOR_NAMES from .named_colors import NAMED_COLORS from .style import Style, merge_styles __all__ = [ 'default_ui_style', 'default_pygments_style', ] #: Default styling. Mapping from classnames to their style definition. PROMPT_TOOLKIT_STYLE = [ # Highlighting of search matches in document. ('search', 'bg:ansibrightyellow ansiblack'), ('search.current', ''), # Incremental search. ('incsearch', ''), ('incsearch.current', 'reverse'), # Highlighting of select text in document. ('selected', 'reverse'), ('cursor-column', 'bg:#dddddd'), ('cursor-line', 'underline'), ('color-column', 'bg:#ccaacc'), # Highlighting of matching brackets. ('matching-bracket', ''), ('matching-bracket.other', '#000000 bg:#aacccc'), ('matching-bracket.cursor', '#ff8888 bg:#880000'), # Styling of other cursors, in case of block editing. ('multiple-cursors', '#000000 bg:#ccccaa'), # Line numbers. ('line-number', '#888888'), ('line-number.current', 'bold'), ('tilde', '#8888ff'), # Default prompt. ('prompt', ''), ('prompt.arg', 'noinherit'), ('prompt.arg.text', ''), ('prompt.search', 'noinherit'), ('prompt.search.text', ''), # Search toolbar. ('search-toolbar', 'bold'), ('search-toolbar.text', 'nobold'), # System toolbar ('system-toolbar', 'bold'), ('system-toolbar.text', 'nobold'), # "arg" toolbar. ('arg-toolbar', 'bold'), ('arg-toolbar.text', 'nobold'), # Validation toolbar. ('validation-toolbar', 'bg:#550000 #ffffff'), ('window-too-small', 'bg:#550000 #ffffff'), # Completions toolbar. ('completion-toolbar', 'bg:#bbbbbb #000000'), ('completion-toolbar.arrow', 'bg:#bbbbbb #000000 bold'), ('completion-toolbar.completion', 'bg:#bbbbbb #000000'), ('completion-toolbar.completion.current', 'bg:#444444 #ffffff'), # Completions menu. ('completion-menu', 'bg:#bbbbbb #000000'), ('completion-menu.completion', ''), ('completion-menu.completion.current', 'bg:#888888 #ffffff'), ('completion-menu.meta.completion', 'bg:#999999 #000000'), ('completion-menu.meta.completion.current', 'bg:#aaaaaa #000000'), ('completion-menu.multi-column-meta', 'bg:#aaaaaa #000000'), # Fuzzy matches in completion menu (for FuzzyCompleter). ('completion-menu.completion fuzzymatch.outside', 'fg:#444444'), ('completion-menu.completion fuzzymatch.inside', 'bold'), ('completion-menu.completion fuzzymatch.inside.character', 'underline'), ('completion-menu.completion.current fuzzymatch.outside', 'fg:default'), ('completion-menu.completion.current fuzzymatch.inside', 'nobold'), # Styling of readline-like completions. ('readline-like-completions', ''), ('readline-like-completions.completion', ''), ('readline-like-completions.completion fuzzymatch.outside', '#888888'), ('readline-like-completions.completion fuzzymatch.inside', ''), ('readline-like-completions.completion fuzzymatch.inside.character', 'underline'), # Scrollbars. ('scrollbar.background', 'bg:#aaaaaa'), ('scrollbar.button', 'bg:#444444'), ('scrollbar.arrow', 'noinherit bold'), # Start/end of scrollbars. Adding 'underline' here provides a nice little # detail to the progress bar, but it doesn't look good on all terminals. # ('scrollbar.start', 'underline #ffffff'), # ('scrollbar.end', 'underline #000000'), # Auto suggestion text. ('auto-suggestion', '#666666'), # Trailing whitespace and tabs. ('trailing-whitespace', '#999999'), ('tab', '#999999'), # When Control-C/D has been pressed. Grayed. ('aborting', '#888888 bg:default noreverse noitalic nounderline noblink'), ('exiting', '#888888 bg:default noreverse noitalic nounderline noblink'), # Entering a Vi digraph. ('digraph', '#4444ff'), # Control characters, like ^C, ^X. ('control-character', 'ansiblue'), # Non-breaking space. ('nbsp', 'underline ansiyellow'), # Default styling of HTML elements. ('i', 'italic'), ('u', 'underline'), ('b', 'bold'), ('em', 'italic'), ('strong', 'bold'), ('hidden', 'hidden'), # It should be possible to use the style names in HTML. # ... or .... ('italic', 'italic'), ('underline', 'underline'), ('bold', 'bold'), ('reverse', 'reverse'), ('noitalic', 'noitalic'), ('nounderline', 'nounderline'), ('nobold', 'nobold'), ('noreverse', 'noreverse'), # Prompt bottom toolbar ('bottom-toolbar', 'reverse'), ] # Style that will turn for instance the class 'red' into 'red'. COLORS_STYLE = [ (name, 'fg:' + name) for name in ANSI_COLOR_NAMES ] + [ (name.lower(), 'fg:' + name) for name in NAMED_COLORS ] WIDGETS_STYLE = [ # Dialog windows. ('dialog', 'bg:#4444ff'), ('dialog.body', 'bg:#ffffff #000000'), ('dialog.body text-area', 'bg:#cccccc'), ('dialog.body text-area last-line', 'underline'), ('dialog frame.label', '#ff0000 bold'), # Scrollbars in dialogs. ('dialog.body scrollbar.background', ''), ('dialog.body scrollbar.button', 'bg:#000000'), ('dialog.body scrollbar.arrow', ''), ('dialog.body scrollbar.start', 'nounderline'), ('dialog.body scrollbar.end', 'nounderline'), # Buttons. ('button', ''), ('button.arrow', 'bold'), ('button.focused', 'bg:#aa0000 #ffffff'), # Menu bars. ('menu-bar', 'bg:#aaaaaa #000000'), ('menu-bar.selected-item', 'bg:#ffffff #000000'), ('menu', 'bg:#888888 #ffffff'), ('menu.border', '#aaaaaa'), ('menu.border shadow', '#444444'), # Shadows. ('dialog shadow', 'bg:#000088'), ('dialog.body shadow', 'bg:#aaaaaa'), ('progress-bar', 'bg:#000088'), ('progress-bar.used', 'bg:#ff0000'), ] # The default Pygments style, include this by default in case a Pygments lexer # is used. PYGMENTS_DEFAULT_STYLE = { 'pygments.whitespace': "#bbbbbb", 'pygments.comment': "italic #408080", 'pygments.comment.preproc': "noitalic #bc7a00", 'pygments.keyword': "bold #008000", 'pygments.keyword.pseudo': "nobold", 'pygments.keyword.type': "nobold #b00040", 'pygments.operator': "#666666", 'pygments.operator.word': "bold #aa22ff", 'pygments.name.builtin': "#008000", 'pygments.name.function': "#0000ff", 'pygments.name.class': "bold #0000ff", 'pygments.name.namespace': "bold #0000ff", 'pygments.name.exception': "bold #d2413a", 'pygments.name.variable': "#19177c", 'pygments.name.constant': "#880000", 'pygments.name.label': "#a0a000", 'pygments.name.entity': "bold #999999", 'pygments.name.attribute': "#7d9029", 'pygments.name.tag': "bold #008000", 'pygments.name.decorator': "#aa22ff", # Note: In Pygments, Token.String is an alias for Token.Literal.String, # and Token.Number as an alias for Token.Literal.Number. 'pygments.literal.string': "#ba2121", 'pygments.literal.string.doc': "italic", 'pygments.literal.string.interpol': "bold #bb6688", 'pygments.literal.string.escape': "bold #bb6622", 'pygments.literal.string.regex': "#bb6688", 'pygments.literal.string.symbol': "#19177c", 'pygments.literal.string.other': "#008000", 'pygments.literal.number': "#666666", 'pygments.generic.heading': "bold #000080", 'pygments.generic.subheading': "bold #800080", 'pygments.generic.deleted': "#a00000", 'pygments.generic.inserted': "#00a000", 'pygments.generic.error': "#ff0000", 'pygments.generic.emph': "italic", 'pygments.generic.strong': "bold", 'pygments.generic.prompt': "bold #000080", 'pygments.generic.output': "#888", 'pygments.generic.traceback': "#04d", 'pygments.error': "border:#ff0000", } @memoized() def default_ui_style(): """ Create a default `Style` object. """ return merge_styles([ Style(PROMPT_TOOLKIT_STYLE), Style(COLORS_STYLE), Style(WIDGETS_STYLE), ]) @memoized() def default_pygments_style(): """ Create a `Style` object that contains the default Pygments style. """ return Style.from_dict(PYGMENTS_DEFAULT_STYLE) prompt_toolkit-2.0.10/prompt_toolkit/styles/named_colors.py0000644000175100017510000001355113545407204025751 0ustar jonathanjonathan00000000000000""" All modern web browsers support these 140 color names. Taken from: https://www.w3schools.com/colors/colors_names.asp """ from __future__ import unicode_literals __all__ = [ 'NAMED_COLORS', ] NAMED_COLORS = { 'AliceBlue': '#f0f8ff', 'AntiqueWhite': '#faebd7', 'Aqua': '#00ffff', 'Aquamarine': '#7fffd4', 'Azure': '#f0ffff', 'Beige': '#f5f5dc', 'Bisque': '#ffe4c4', 'Black': '#000000', 'BlanchedAlmond': '#ffebcd', 'Blue': '#0000ff', 'BlueViolet': '#8a2be2', 'Brown': '#a52a2a', 'BurlyWood': '#deb887', 'CadetBlue': '#5f9ea0', 'Chartreuse': '#7fff00', 'Chocolate': '#d2691e', 'Coral': '#ff7f50', 'CornflowerBlue': '#6495ed', 'Cornsilk': '#fff8dc', 'Crimson': '#dc143c', 'Cyan': '#00ffff', 'DarkBlue': '#00008b', 'DarkCyan': '#008b8b', 'DarkGoldenRod': '#b8860b', 'DarkGray': '#a9a9a9', 'DarkGreen': '#006400', 'DarkGrey': '#a9a9a9', 'DarkKhaki': '#bdb76b', 'DarkMagenta': '#8b008b', 'DarkOliveGreen': '#556b2f', 'DarkOrange': '#ff8c00', 'DarkOrchid': '#9932cc', 'DarkRed': '#8b0000', 'DarkSalmon': '#e9967a', 'DarkSeaGreen': '#8fbc8f', 'DarkSlateBlue': '#483d8b', 'DarkSlateGray': '#2f4f4f', 'DarkSlateGrey': '#2f4f4f', 'DarkTurquoise': '#00ced1', 'DarkViolet': '#9400d3', 'DeepPink': '#ff1493', 'DeepSkyBlue': '#00bfff', 'DimGray': '#696969', 'DimGrey': '#696969', 'DodgerBlue': '#1e90ff', 'FireBrick': '#b22222', 'FloralWhite': '#fffaf0', 'ForestGreen': '#228b22', 'Fuchsia': '#ff00ff', 'Gainsboro': '#dcdcdc', 'GhostWhite': '#f8f8ff', 'Gold': '#ffd700', 'GoldenRod': '#daa520', 'Gray': '#808080', 'Green': '#008000', 'GreenYellow': '#adff2f', 'Grey': '#808080', 'HoneyDew': '#f0fff0', 'HotPink': '#ff69b4', 'IndianRed': '#cd5c5c', 'Indigo': '#4b0082', 'Ivory': '#fffff0', 'Khaki': '#f0e68c', 'Lavender': '#e6e6fa', 'LavenderBlush': '#fff0f5', 'LawnGreen': '#7cfc00', 'LemonChiffon': '#fffacd', 'LightBlue': '#add8e6', 'LightCoral': '#f08080', 'LightCyan': '#e0ffff', 'LightGoldenRodYellow': '#fafad2', 'LightGray': '#d3d3d3', 'LightGreen': '#90ee90', 'LightGrey': '#d3d3d3', 'LightPink': '#ffb6c1', 'LightSalmon': '#ffa07a', 'LightSeaGreen': '#20b2aa', 'LightSkyBlue': '#87cefa', 'LightSlateGray': '#778899', 'LightSlateGrey': '#778899', 'LightSteelBlue': '#b0c4de', 'LightYellow': '#ffffe0', 'Lime': '#00ff00', 'LimeGreen': '#32cd32', 'Linen': '#faf0e6', 'Magenta': '#ff00ff', 'Maroon': '#800000', 'MediumAquaMarine': '#66cdaa', 'MediumBlue': '#0000cd', 'MediumOrchid': '#ba55d3', 'MediumPurple': '#9370db', 'MediumSeaGreen': '#3cb371', 'MediumSlateBlue': '#7b68ee', 'MediumSpringGreen': '#00fa9a', 'MediumTurquoise': '#48d1cc', 'MediumVioletRed': '#c71585', 'MidnightBlue': '#191970', 'MintCream': '#f5fffa', 'MistyRose': '#ffe4e1', 'Moccasin': '#ffe4b5', 'NavajoWhite': '#ffdead', 'Navy': '#000080', 'OldLace': '#fdf5e6', 'Olive': '#808000', 'OliveDrab': '#6b8e23', 'Orange': '#ffa500', 'OrangeRed': '#ff4500', 'Orchid': '#da70d6', 'PaleGoldenRod': '#eee8aa', 'PaleGreen': '#98fb98', 'PaleTurquoise': '#afeeee', 'PaleVioletRed': '#db7093', 'PapayaWhip': '#ffefd5', 'PeachPuff': '#ffdab9', 'Peru': '#cd853f', 'Pink': '#ffc0cb', 'Plum': '#dda0dd', 'PowderBlue': '#b0e0e6', 'Purple': '#800080', 'RebeccaPurple': '#663399', 'Red': '#ff0000', 'RosyBrown': '#bc8f8f', 'RoyalBlue': '#4169e1', 'SaddleBrown': '#8b4513', 'Salmon': '#fa8072', 'SandyBrown': '#f4a460', 'SeaGreen': '#2e8b57', 'SeaShell': '#fff5ee', 'Sienna': '#a0522d', 'Silver': '#c0c0c0', 'SkyBlue': '#87ceeb', 'SlateBlue': '#6a5acd', 'SlateGray': '#708090', 'SlateGrey': '#708090', 'Snow': '#fffafa', 'SpringGreen': '#00ff7f', 'SteelBlue': '#4682b4', 'Tan': '#d2b48c', 'Teal': '#008080', 'Thistle': '#d8bfd8', 'Tomato': '#ff6347', 'Turquoise': '#40e0d0', 'Violet': '#ee82ee', 'Wheat': '#f5deb3', 'White': '#ffffff', 'WhiteSmoke': '#f5f5f5', 'Yellow': '#ffff00', 'YellowGreen': '#9acd32', } prompt_toolkit-2.0.10/prompt_toolkit/styles/pygments.py0000644000175100017510000000352713545407204025154 0ustar jonathanjonathan00000000000000""" Adaptor for building prompt_toolkit styles, starting from a Pygments style. Usage:: from pygments.styles.tango import TangoStyle style = style_from_pygments_cls(pygments_style_cls=TangoStyle) """ from __future__ import absolute_import, unicode_literals from .style import Style __all__ = [ 'style_from_pygments_cls', 'style_from_pygments_dict', 'pygments_token_to_classname', ] def style_from_pygments_cls(pygments_style_cls): """ Shortcut to create a :class:`.Style` instance from a Pygments style class and a style dictionary. Example:: from prompt_toolkit.styles.from_pygments import style_from_pygments_cls from pygments.styles import get_style_by_name style = style_from_pygments_cls(get_style_by_name('monokai')) :param pygments_style_cls: Pygments style class to start from. """ # Import inline. from pygments.style import Style as pygments_Style assert issubclass(pygments_style_cls, pygments_Style) return style_from_pygments_dict(pygments_style_cls.styles) def style_from_pygments_dict(pygments_dict): """ Create a :class:`.Style` instance from a Pygments style dictionary. (One that maps Token objects to style strings.) """ assert hasattr(pygments_dict, 'items') # collections.abc.Mapping only available on Python 3. pygments_style = [] for token, style in pygments_dict.items(): pygments_style.append((pygments_token_to_classname(token), style)) return Style(pygments_style) def pygments_token_to_classname(token): """ Turn e.g. `Token.Name.Exception` into `'pygments.name.exception'`. (Our Pygments lexer will also turn the tokens that pygments produces in a prompt_toolkit list of fragments that match these styling rules.) """ parts = ('pygments', ) + token return '.'.join(parts).lower() prompt_toolkit-2.0.10/prompt_toolkit/styles/style.py0000644000175100017510000003051413545407204024442 0ustar jonathanjonathan00000000000000""" Tool for creating styles from a dictionary. """ from __future__ import absolute_import, unicode_literals import itertools import re import sys from prompt_toolkit.cache import SimpleCache from .base import ( ANSI_COLOR_NAMES, ANSI_COLOR_NAMES_ALIASES, DEFAULT_ATTRS, Attrs, BaseStyle, ) from .named_colors import NAMED_COLORS __all__ = [ 'Style', 'parse_color', 'Priority', 'merge_styles', ] _named_colors_lowercase = dict( (k.lower(), v.lstrip('#')) for k, v in NAMED_COLORS.items()) def parse_color(text): """ Parse/validate color format. Like in Pygments, but also support the ANSI color names. (These will map to the colors of the 16 color palette.) """ # ANSI color names. if text in ANSI_COLOR_NAMES: return text if text in ANSI_COLOR_NAMES_ALIASES: return ANSI_COLOR_NAMES_ALIASES[text] # 140 named colors. try: # Replace by 'hex' value. return _named_colors_lowercase[text.lower()] except KeyError: pass # Hex codes. if text[0:1] == '#': col = text[1:] # Keep this for backwards-compatibility (Pygments does it). # I don't like the '#' prefix for named colors. if col in ANSI_COLOR_NAMES: return col elif col in ANSI_COLOR_NAMES_ALIASES: return ANSI_COLOR_NAMES_ALIASES[col] # 6 digit hex color. elif len(col) == 6: return col # 3 digit hex color. elif len(col) == 3: return col[0] * 2 + col[1] * 2 + col[2] * 2 # Default. elif text in ('', 'default'): return text raise ValueError('Wrong color format %r' % text) # Attributes, when they are not filled in by a style. None means that we take # the value from the parent. _EMPTY_ATTRS = Attrs(color=None, bgcolor=None, bold=None, underline=None, italic=None, blink=None, reverse=None, hidden=None) def _expand_classname(classname): """ Split a single class name at the `.` operator, and build a list of classes. E.g. 'a.b.c' becomes ['a', 'a.b', 'a.b.c'] """ result = [] parts = classname.split('.') for i in range(1, len(parts) + 1): result.append('.'.join(parts[:i]).lower()) return result def _parse_style_str(style_str): """ Take a style string, e.g. 'bg:red #88ff00 class:title' and return a `Attrs` instance. """ # Start from default Attrs. if 'noinherit' in style_str: attrs = DEFAULT_ATTRS else: attrs = _EMPTY_ATTRS # Now update with the given attributes. for part in style_str.split(): if part == 'noinherit': pass elif part == 'bold': attrs = attrs._replace(bold=True) elif part == 'nobold': attrs = attrs._replace(bold=False) elif part == 'italic': attrs = attrs._replace(italic=True) elif part == 'noitalic': attrs = attrs._replace(italic=False) elif part == 'underline': attrs = attrs._replace(underline=True) elif part == 'nounderline': attrs = attrs._replace(underline=False) # prompt_toolkit extensions. Not in Pygments. elif part == 'blink': attrs = attrs._replace(blink=True) elif part == 'noblink': attrs = attrs._replace(blink=False) elif part == 'reverse': attrs = attrs._replace(reverse=True) elif part == 'noreverse': attrs = attrs._replace(reverse=False) elif part == 'hidden': attrs = attrs._replace(hidden=True) elif part == 'nohidden': attrs = attrs._replace(hidden=False) # Pygments properties that we ignore. elif part in ('roman', 'sans', 'mono'): pass elif part.startswith('border:'): pass # Ignore pieces in between square brackets. This is internal stuff. # Like '[transparent]' or '[set-cursor-position]'. elif part.startswith('[') and part.endswith(']'): pass # Colors. elif part.startswith('bg:'): attrs = attrs._replace(bgcolor=parse_color(part[3:])) elif part.startswith('fg:'): # The 'fg:' prefix is optional. attrs = attrs._replace(color=parse_color(part[3:])) else: attrs = attrs._replace(color=parse_color(part)) return attrs CLASS_NAMES_RE = re.compile(r'^[a-z0-9.\s_-]*$') # This one can't contain a comma! class Priority: """ The priority of the rules, when a style is created from a dictionary. In a `Style`, rules that are defined later will always override previous defined rules, however in a dictionary, the key order was arbitrary before Python 3.6. This means that the style could change at random between rules. We have two options: - `DICT_KEY_ORDER`: This means, iterate through the dictionary, and take the key/value pairs in order as they come. This is a good option if you have Python >3.6. Rules at the end will override rules at the beginning. - `MOST_PRECISE`: keys that are defined with most precision will get higher priority. (More precise means: more elements.) """ DICT_KEY_ORDER = 'KEY_ORDER' MOST_PRECISE = 'MOST_PRECISE' _ALL = [DICT_KEY_ORDER, MOST_PRECISE] # In the latest python verions, we take the dictionary ordering like it is, # In older versions, we sort by by precision. If you need to write code that # runs on all Python versions, it's best to sort them manually, with the most # precise rules at the bottom. if sys.version_info >= (3, 6): default_priority = Priority.DICT_KEY_ORDER else: default_priority = Priority.MOST_PRECISE class Style(BaseStyle): """ Create a ``Style`` instance from a list of style rules. The `style_rules` is supposed to be a list of ('classnames', 'style') tuples. The classnames are a whitespace separated string of class names and the style string is just like a Pygments style definition, but with a few additions: it supports 'reverse' and 'blink'. Later rules always override previous rules. Usage:: Style([ ('title', '#ff0000 bold underline'), ('something-else', 'reverse'), ('class1 class2', 'reverse'), ]) The ``from_dict`` classmethod is similar, but takes a dictionary as input. """ def __init__(self, style_rules): assert isinstance(style_rules, list) class_names_and_attrs = [] # Loop through the rules in the order they were defined. # Rules that are defined later get priority. for class_names, style_str in style_rules: assert CLASS_NAMES_RE.match(class_names), repr(class_names) # The order of the class names doesn't matter. # (But the order of rules does matter.) class_names = frozenset(class_names.lower().split()) attrs = _parse_style_str(style_str) class_names_and_attrs.append((class_names, attrs)) self._style_rules = style_rules self.class_names_and_attrs = class_names_and_attrs @property def style_rules(self): return self._style_rules @classmethod def from_dict(cls, style_dict, priority=default_priority): """ :param style_dict: Style dictionary. :param priority: `Priority` value. """ assert priority in Priority._ALL if priority == Priority.MOST_PRECISE: def key(item): # Split on '.' and whitespace. Count elements. return sum(len(i.split('.')) for i in item[0].split()) return cls(sorted(style_dict.items(), key=key)) else: return cls(list(style_dict.items())) def get_attrs_for_style_str(self, style_str, default=DEFAULT_ATTRS): """ Get `Attrs` for the given style string. """ list_of_attrs = [default] class_names = set() # Apply default styling. for names, attr in self.class_names_and_attrs: if not names: list_of_attrs.append(attr) # Go from left to right through the style string. Things on the right # take precedence. for part in style_str.split(): # This part represents a class. # Do lookup of this class name in the style definition, as well # as all class combinations that we have so far. if part.startswith('class:'): # Expand all class names (comma separated list). new_class_names = [] for p in part[6:].lower().split(','): new_class_names.extend(_expand_classname(p)) for new_name in new_class_names: # Build a set of all possible class combinations to be applied. combos = set() combos.add(frozenset([new_name])) for count in range(1, len(class_names) + 1): for c2 in itertools.combinations(class_names, count): combos.add(frozenset(c2 + (new_name, ))) # Apply the styles that match these class names. for names, attr in self.class_names_and_attrs: if names in combos: list_of_attrs.append(attr) class_names.add(new_name) # Process inline style. else: inline_attrs = _parse_style_str(part) list_of_attrs.append(inline_attrs) return _merge_attrs(list_of_attrs) def invalidation_hash(self): return id(self.class_names_and_attrs) def _merge_attrs(list_of_attrs): """ Take a list of :class:`.Attrs` instances and merge them into one. Every `Attr` in the list can override the styling of the previous one. So, the last one has highest priority. """ def _or(*values): " Take first not-None value, starting at the end. " for v in values[::-1]: if v is not None: return v return Attrs( color=_or('', *[a.color for a in list_of_attrs]), bgcolor=_or('', *[a.bgcolor for a in list_of_attrs]), bold=_or(False, *[a.bold for a in list_of_attrs]), underline=_or(False, *[a.underline for a in list_of_attrs]), italic=_or(False, *[a.italic for a in list_of_attrs]), blink=_or(False, *[a.blink for a in list_of_attrs]), reverse=_or(False, *[a.reverse for a in list_of_attrs]), hidden=_or(False, *[a.hidden for a in list_of_attrs])) def merge_styles(styles): """ Merge multiple `Style` objects. """ styles = [s for s in styles if s is not None] return _MergedStyle(styles) class _MergedStyle(BaseStyle): """ Merge multiple `Style` objects into one. This is supposed to ensure consistency: if any of the given styles changes, then this style will be updated. """ # NOTE: previously, we used an algorithm where we did not generate the # combined style. Instead this was a proxy that called one style # after the other, passing the outcome of the previous style as the # default for the next one. This did not work, because that way, the # priorities like described in the `Style` class don't work. # 'class:aborted' was for instance never displayed in gray, because # the next style specified a default color for any text. (The # explicit styling of class:aborted should have taken priority, # because it was more precise.) def __init__(self, styles): assert all(isinstance(style, BaseStyle) for style in styles) self.styles = styles self._style = SimpleCache(maxsize=1) @property def _merged_style(self): " The `Style` object that has the other styles merged together. " def get(): return Style(self.style_rules) return self._style.get(self.invalidation_hash(), get) @property def style_rules(self): style_rules = [] for s in self.styles: style_rules.extend(s.style_rules) return style_rules def get_attrs_for_style_str(self, style_str, default=DEFAULT_ATTRS): return self._merged_style.get_attrs_for_style_str(style_str, default) def invalidation_hash(self): return tuple(s.invalidation_hash() for s in self.styles) prompt_toolkit-2.0.10/prompt_toolkit/styles/style_transformation.py0000644000175100017510000002702513545407204027573 0ustar jonathanjonathan00000000000000""" Collection of style transformations. Think of it as a kind of color post processing after the rendering is done. This could be used for instance to change the contrast/saturation; swap light and dark colors or even change certain colors for other colors. When the UI is rendered, these transformations can be applied right after the style strings are turned into `Attrs` objects that represent the actual formatting. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from colorsys import hls_to_rgb, rgb_to_hls from six import with_metaclass from prompt_toolkit.cache import memoized from prompt_toolkit.filters import to_filter from prompt_toolkit.utils import to_float, to_str from .base import ANSI_COLOR_NAMES from .style import parse_color __all__ = [ 'StyleTransformation', 'SwapLightAndDarkStyleTransformation', 'ReverseStyleTransformation', 'SetDefaultColorStyleTransformation', 'AdjustBrightnessStyleTransformation', 'DummyStyleTransformation', 'ConditionalStyleTransformation', 'DynamicStyleTransformation', 'merge_style_transformations', ] class StyleTransformation(with_metaclass(ABCMeta, object)): """ Base class for any style transformation. """ @abstractmethod def transform_attrs(self, attrs): """ Take an `Attrs` object and return a new `Attrs` object. Remember that the color formats can be either "ansi..." or a 6 digit lowercase hexadecimal color (without '#' prefix). """ def invalidation_hash(self): """ When this changes, the cache should be invalidated. """ return '%s-%s' % (self.__class__.__name__, id(self)) class SwapLightAndDarkStyleTransformation(StyleTransformation): """ Turn dark colors into light colors and the other way around. This is meant to make color schemes that work on a dark background usable on a light background (and the other way around). 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 colors are considered individually. Also notice that when is used somewhere and no colors are given in particular (like what is the default for the bottom toolbar), then this doesn't change anything. This is what makes sense, because when the 'default' color is chosen, it's what works best for the terminal, and reverse works good with that. """ def transform_attrs(self, attrs): """ Return the `Attrs` used when opposite luminosity should be used. """ # Reverse colors. attrs = attrs._replace(color=get_opposite_color(attrs.color)) attrs = attrs._replace(bgcolor=get_opposite_color(attrs.bgcolor)) return attrs class ReverseStyleTransformation(StyleTransformation): """ Swap the 'reverse' attribute. (This is still experimental.) """ def transform_attrs(self, attrs): return attrs._replace(reverse=not attrs.reverse) class SetDefaultColorStyleTransformation(StyleTransformation): """ Set default foreground/background color for output that doesn't specify anything. This is useful for overriding the terminal default colors. :param fg: Color string or callable that returns a color string for the foreground. :param bg: Like `fg`, but for the background. """ def __init__(self, fg, bg): self.fg = fg self.bg = bg def transform_attrs(self, attrs): if attrs.bgcolor in ('', 'default'): attrs = attrs._replace(bgcolor=parse_color(to_str(self.bg))) if attrs.color in ('', 'default'): attrs = attrs._replace(color=parse_color(to_str(self.fg))) return attrs def invalidation_hash(self): return ( 'set-default-color', to_str(self.fg), to_str(self.bg), ) class AdjustBrightnessStyleTransformation(StyleTransformation): """ Adjust the brightness to improve the rendering on either dark or light backgrounds. For dark backgrounds, it's best to increase `min_brightness`. For light backgrounds it's best to decrease `max_brightness`. Usually, only one setting is adjusted. This will only change the brightness for text that has a foreground color defined, but no background color. It works best for 256 or true color output. .. note:: Notice that there is no universal way to detect whether the application is running in a light or dark terminal. As a developer of an command line application, you'll have to make this configurable for the user. :param min_brightness: Float between 0.0 and 1.0 or a callable that returns a float. :param max_brightness: Float between 0.0 and 1.0 or a callable that returns a float. """ def __init__(self, min_brightness=0.0, max_brightness=1.0): self.min_brightness = min_brightness self.max_brightness = max_brightness def transform_attrs(self, attrs): min_brightness = to_float(self.min_brightness) max_brightness = to_float(self.max_brightness) assert 0 <= min_brightness <= 1 assert 0 <= max_brightness <= 1 # Don't do anything if the whole brightness range is acceptable. # This also avoids turning ansi colors into RGB sequences. if min_brightness == 0.0 and max_brightness == 1.0: return attrs # If a foreground color is given without a background color. no_background = not attrs.bgcolor or attrs.bgcolor == 'default' has_fgcolor = attrs.color and attrs.color != 'ansidefault' if has_fgcolor and no_background: # Calculate new RGB values. r, g, b = self._color_to_rgb(attrs.color) hue, brightness, saturation = rgb_to_hls(r, g, b) brightness = self._interpolate_brightness( brightness, min_brightness, max_brightness) r, g, b = hls_to_rgb(hue, brightness, saturation) new_color = '%02x%02x%02x' % ( int(r * 255), int(g * 255), int(b * 255)) attrs = attrs._replace(color=new_color) return attrs def _color_to_rgb(self, color): """ Parse `style.Attrs` color into RGB tuple. """ # Do RGB lookup for ANSI colors. try: from prompt_toolkit.output.vt100 import ANSI_COLORS_TO_RGB r, g, b = ANSI_COLORS_TO_RGB[color] return r / 255.0, g / 255.0, b / 255.0 except KeyError: pass # Parse RRGGBB format. r = int(color[0:2], 16) / 255.0 g = int(color[2:4], 16) / 255.0 b = int(color[4:6], 16) / 255.0 return r, g, b # NOTE: we don't have to support named colors here. They are already # transformed into RGB values in `style.parse_color`. def _interpolate_brightness(self, value, min_brightness, max_brightness): """ Map the brightness to the (min_brightness..max_brightness) range. """ return ( min_brightness + (max_brightness - min_brightness) * value ) def invalidation_hash(self): return ( 'adjust-brightness', to_float(self.min_brightness), to_float(self.max_brightness) ) class DummyStyleTransformation(StyleTransformation): """ Don't transform anything at all. """ def transform_attrs(self, attrs): return attrs def invalidation_hash(self): # Always return the same hash for these dummy instances. return 'dummy-style-transformation' class DynamicStyleTransformation(StyleTransformation): """ StyleTransformation class that can dynamically returns any `StyleTransformation`. :param get_style_transformation: Callable that returns a :class:`.StyleTransformation` instance. """ def __init__(self, get_style_transformation): assert callable(get_style_transformation) self.get_style_transformation = get_style_transformation def transform_attrs(self, attrs): style_transformation = self.get_style_transformation() or DummyStyleTransformation() return style_transformation.transform_attrs(attrs) def invalidation_hash(self): style_transformation = self.get_style_transformation() or DummyStyleTransformation() return style_transformation.invalidation_hash() class ConditionalStyleTransformation(StyleTransformation): """ Apply the style transformation depending on a condition. """ def __init__(self, style_transformation, filter): assert isinstance(style_transformation, StyleTransformation) self.style_transformation = style_transformation self.filter = to_filter(filter) def transform_attrs(self, attrs): if self.filter(): return self.style_transformation.transform_attrs(attrs) return attrs def invalidation_hash(self): return ( self.filter(), self.style_transformation.invalidation_hash() ) class _MergedStyleTransformation(StyleTransformation): def __init__(self, style_transformations): self.style_transformations = style_transformations def transform_attrs(self, attrs): for transformation in self.style_transformations: attrs = transformation.transform_attrs(attrs) return attrs def invalidation_hash(self): return tuple(t.invalidation_hash() for t in self.style_transformations) def merge_style_transformations(style_transformations): """ Merge multiple transformations together. """ return _MergedStyleTransformation(style_transformations) # Dictionary that maps ANSI color names to their opposite. This is useful for # turning color schemes that are optimized for a black background usable for a # white background. OPPOSITE_ANSI_COLOR_NAMES = { 'ansidefault': 'ansidefault', 'ansiblack': 'ansiwhite', 'ansired': 'ansibrightred', 'ansigreen': 'ansibrightgreen', 'ansiyellow': 'ansibrightyellow', 'ansiblue': 'ansibrightblue', 'ansimagenta': 'ansibrightmagenta', 'ansicyan': 'ansibrightcyan', 'ansigray': 'ansibrightblack', 'ansiwhite': 'ansiblack', 'ansibrightred': 'ansired', 'ansibrightgreen': 'ansigreen', 'ansibrightyellow': 'ansiyellow', 'ansibrightblue': 'ansiblue', 'ansibrightmagenta': 'ansimagenta', 'ansibrightcyan': 'ansicyan', 'ansibrightblack': 'ansigray', } assert set(OPPOSITE_ANSI_COLOR_NAMES.keys()) == set(ANSI_COLOR_NAMES) assert set(OPPOSITE_ANSI_COLOR_NAMES.values()) == set(ANSI_COLOR_NAMES) @memoized() def get_opposite_color(colorname): """ Take a color name in either 'ansi...' format or 6 digit RGB, return the color of opposite luminosity (same hue/saturation). This is used for turning color schemes that work on a light background usable on a dark background. """ # Special values. if colorname in ('', 'default'): return colorname # Try ANSI color names. try: return OPPOSITE_ANSI_COLOR_NAMES[colorname] except KeyError: # Try 6 digit RGB colors. r = int(colorname[:2], 16) / 255.0 g = int(colorname[2:4], 16) / 255.0 b = int(colorname[4:6], 16) / 255.0 h, l, s = rgb_to_hls(r, g, b) l = 1 - l r, g, b = hls_to_rgb(h, l, s) r = int(r * 255) g = int(g * 255) b = int(b * 255) return '%02x%02x%02x' % (r, g, b) prompt_toolkit-2.0.10/prompt_toolkit/token.py0000644000175100017510000000012513545407022023070 0ustar jonathanjonathan00000000000000""" """ __all__ = [ 'ZeroWidthEscape', ] ZeroWidthEscape = '[ZeroWidthEscape]' prompt_toolkit-2.0.10/prompt_toolkit/utils.py0000644000175100017510000002172513545407204023123 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import inspect import os import signal import sys import threading import weakref from collections import deque from functools import partial from six import PY2, text_type from six.moves import range from wcwidth import wcwidth from .cache import memoized __all__ = [ 'Event', 'DummyContext', 'get_cwidth', 'suspend_to_background_supported', 'is_conemu_ansi', 'is_windows', 'in_main_thread', 'take_using_weights', 'test_callable_args', 'to_str', 'to_int', 'to_float', ] class Event(object): """ Simple event to which event handlers can be attached. For instance:: class Cls: def __init__(self): # Define event. The first parameter is the sender. self.event = Event(self) obj = Cls() def handler(sender): pass # Add event handler by using the += operator. obj.event += handler # Fire event. obj.event() """ def __init__(self, sender, handler=None): self.sender = sender self._handlers = [] if handler is not None: self += handler def __call__(self): " Fire event. " for handler in self._handlers: handler(self.sender) def fire(self): " Alias for just calling the event. " self() def add_handler(self, handler): """ Add another handler to this callback. (Handler should be a callable that takes exactly one parameter: the sender object.) """ # Test handler. assert callable(handler) if not _func_takes_one_arg(handler): raise TypeError("%r doesn't take exactly one argument." % handler) # Add to list of event handlers. self._handlers.append(handler) def remove_handler(self, handler): """ Remove a handler from this callback. """ if handler in self._handlers: self._handlers.remove(handler) def __iadd__(self, handler): " `event += handler` notation for adding a handler. " self.add_handler(handler) return self def __isub__(self, handler): " `event -= handler` notation for removing a handler. " self.remove_handler(handler) return self # Cache of signatures. Improves the performance of `test_callable_args`. _signatures_cache = weakref.WeakKeyDictionary() _inspect_signature = getattr(inspect, 'signature', None) # Only on Python 3. def test_callable_args(func, args): """ Return True when this function can be called with the given arguments. """ assert isinstance(args, (list, tuple)) if _inspect_signature is not None: # For Python 3, use inspect.signature. try: sig = _signatures_cache[func] except KeyError: sig = _inspect_signature(func) _signatures_cache[func] = sig try: sig.bind(*args) except TypeError: return False else: return True else: # For older Python versions, fall back to using getargspec # and don't check for `partial`. if isinstance(func, partial): return True spec = inspect.getargspec(func) # Drop the 'self' def drop_self(spec): args, varargs, varkw, defaults = spec if args[0:1] == ['self']: args = args[1:] return inspect.ArgSpec(args, varargs, varkw, defaults) spec = drop_self(spec) # When taking *args, always return True. if spec.varargs is not None: return True # Test whether the given amount of args is between the min and max # accepted argument counts. return len(spec.args) - len(spec.defaults or []) <= len(args) <= len(spec.args) @memoized(maxsize=1024) def _func_takes_one_arg(func): """ Test whether the given function can be called with exactly one argument. """ return test_callable_args(func, [None]) class DummyContext(object): """ (contextlib.nested is not available on Py3) """ def __enter__(self): pass def __exit__(self, *a): pass class _CharSizesCache(dict): """ Cache for wcwidth sizes. """ LONG_STRING_MIN_LEN = 64 # Minimum string length for considering it long. MAX_LONG_STRINGS = 16 # Maximum number of long strings to remember. def __init__(self): super(_CharSizesCache, self).__init__() # Keep track of the "long" strings in this cache. self._long_strings = deque() def __missing__(self, string): # Note: We use the `max(0, ...` because some non printable control # characters, like e.g. Ctrl-underscore get a -1 wcwidth value. # It can be possible that these characters end up in the input # text. if len(string) == 1: result = max(0, wcwidth(string)) else: result = sum(self[c] for c in string) # Store in cache. self[string] = result # Rotate long strings. # (It's hard to tell what we can consider short...) if len(string) > self.LONG_STRING_MIN_LEN: long_strings = self._long_strings long_strings.append(string) if len(long_strings) > self.MAX_LONG_STRINGS: key_to_remove = long_strings.popleft() if key_to_remove in self: del self[key_to_remove] return result _CHAR_SIZES_CACHE = _CharSizesCache() def get_cwidth(string): """ Return width of a string. Wrapper around ``wcwidth``. """ return _CHAR_SIZES_CACHE[string] def suspend_to_background_supported(): """ Returns `True` when the Python implementation supports suspend-to-background. This is typically `False' on Windows systems. """ return hasattr(signal, 'SIGTSTP') def is_windows(): """ True when we are using Windows. """ return sys.platform.startswith('win') # E.g. 'win32', not 'darwin' or 'linux2' def is_windows_vt100_supported(): """ True when we are using Windows, but VT100 escape sequences are supported. """ # Import needs to be inline. Windows libraries are not always available. from prompt_toolkit.output.windows10 import is_win_vt100_enabled return is_windows() and is_win_vt100_enabled() def is_conemu_ansi(): """ True when the ConEmu Windows console is used. """ return is_windows() and os.environ.get('ConEmuANSI', 'OFF') == 'ON' def in_main_thread(): """ True when the current thread is the main thread. """ return threading.current_thread().__class__.__name__ == '_MainThread' def get_term_environment_variable(): " Return the $TERM environment variable. " term = os.environ.get('TERM', '') if PY2: term = term.decode('utf-8') return term def take_using_weights(items, weights): """ Generator that keeps yielding items from the items list, in proportion to their weight. For instance:: # Getting the first 70 items from this generator should have yielded 10 # times A, 20 times B and 40 times C, all distributed equally.. take_using_weights(['A', 'B', 'C'], [5, 10, 20]) :param items: List of items to take from. :param weights: Integers representing the weight. (Numbers have to be integers, not floats.) """ assert isinstance(items, list) assert isinstance(weights, list) assert all(isinstance(i, int) for i in weights) assert len(items) == len(weights) assert len(items) > 0 # Remove items with zero-weight. items2 = [] weights2 = [] for i, w in zip(items, weights): if w > 0: items2.append(i) weights2.append(w) items = items2 weights = weights2 # Make sure that we have some items left. if not items: raise ValueError("Did't got any items with a positive weight.") # already_taken = [0 for i in items] item_count = len(items) max_weight = max(weights) i = 0 while True: # Each iteration of this loop, we fill up until by (total_weight/max_weight). adding = True while adding: adding = False for item_i, item, weight in zip(range(item_count), items, weights): if already_taken[item_i] < i * weight / float(max_weight): yield item already_taken[item_i] += 1 adding = True i += 1 def to_str(value): " Turn callable or string into string. " if callable(value): return to_str(value()) else: return text_type(value) def to_int(value): " Turn callable or int into int. " if callable(value): return to_int(value()) else: return int(value) def to_float(value): " Turn callable or float into float. " if callable(value): return to_float(value()) else: return float(value) prompt_toolkit-2.0.10/prompt_toolkit/validation.py0000644000175100017510000001332213545407204024107 0ustar jonathanjonathan00000000000000""" Input validation for a `Buffer`. (Validators will be called before accepting input.) """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import text_type, with_metaclass from .eventloop import Future, run_in_executor from .filters import to_filter __all__ = [ 'ConditionalValidator', 'ValidationError', 'Validator', 'ThreadedValidator', 'DummyValidator', 'DynamicValidator', ] class ValidationError(Exception): """ Error raised by :meth:`.Validator.validate`. :param cursor_position: The cursor position where the error occurred. :param message: Text. """ def __init__(self, cursor_position=0, message=''): super(ValidationError, self).__init__(message) self.cursor_position = cursor_position self.message = message def __repr__(self): return '%s(cursor_position=%r, message=%r)' % ( self.__class__.__name__, self.cursor_position, self.message) class Validator(with_metaclass(ABCMeta, object)): """ Abstract base class for an input validator. A validator is typically created in one of the following two ways: - Either by overriding this class and implementing the `validate` method. - Or by passing a callable to `Validator.from_callable`. If the validation takes some time and needs to happen in a background thread, this can be wrapped in a :class:`.ThreadedValidator`. """ @abstractmethod def validate(self, document): """ Validate the input. If invalid, this should raise a :class:`.ValidationError`. :param document: :class:`~prompt_toolkit.document.Document` instance. """ pass def get_validate_future(self, document): """ Return a `Future` which is set when the validation is ready. This function can be overloaded in order to provide an asynchronous implementation. """ try: self.validate(document) except ValidationError as e: return Future.fail(e) else: return Future.succeed(None) @classmethod def from_callable(cls, validate_func, error_message='Invalid input', move_cursor_to_end=False): """ Create a validator from a simple validate callable. E.g.: .. code:: python def is_valid(text): return text in ['hello', 'world'] Validator.from_callable(is_valid, error_message='Invalid input') :param validate_func: Callable that takes the input string, and returns `True` if the input is valid input. :param error_message: Message to be displayed if the input is invalid. :param move_cursor_to_end: Move the cursor to the end of the input, if the input is invalid. """ return _ValidatorFromCallable( validate_func, error_message, move_cursor_to_end) class _ValidatorFromCallable(Validator): """ Validate input from a simple callable. """ def __init__(self, func, error_message, move_cursor_to_end): assert callable(func) assert isinstance(error_message, text_type) self.func = func self.error_message = error_message self.move_cursor_to_end = move_cursor_to_end def __repr__(self): return 'Validator.from_callable(%r)' % (self.func, ) def validate(self, document): if not self.func(document.text): if self.move_cursor_to_end: index = len(document.text) else: index = 0 raise ValidationError(cursor_position=index, message=self.error_message) class ThreadedValidator(Validator): """ Wrapper that runs input validation in a thread. (Use this to prevent the user interface from becoming unresponsive if the input validation takes too much time.) """ def __init__(self, validator): assert isinstance(validator, Validator) self.validator = validator def validate(self, document): return self.validator.validate(document) def get_validate_future(self, document): """ Run the `validate` function in a thread. """ def run_validation_thread(): return self.validate(document) f = run_in_executor(run_validation_thread) return f class DummyValidator(Validator): """ Validator class that accepts any input. """ def validate(self, document): pass # Don't raise any exception. class ConditionalValidator(Validator): """ Validator that can be switched on/off according to a filter. (This wraps around another validator.) """ def __init__(self, validator, filter): assert isinstance(validator, Validator) self.validator = validator self.filter = to_filter(filter) def validate(self, document): # Call the validator only if the filter is active. if self.filter(): self.validator.validate(document) class DynamicValidator(Validator): """ Validator class that can dynamically returns any Validator. :param get_validator: Callable that returns a :class:`.Validator` instance. """ def __init__(self, get_validator): assert callable(get_validator) self.get_validator = get_validator def validate(self, document): validator = self.get_validator() or DummyValidator() assert isinstance(validator, Validator) return validator.validate(document) def get_validate_future(self, document): validator = self.get_validator() or DummyValidator() assert isinstance(validator, Validator) return validator.get_validate_future(document) prompt_toolkit-2.0.10/prompt_toolkit/widgets/0000755000175100017510000000000013545410361023046 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit/widgets/__init__.py0000644000175100017510000000224313545407204025162 0ustar jonathanjonathan00000000000000""" Collection of reusable components for building full screen applications. These are higher level abstractions on top of the `prompt_toolkit.layout` module. Most of these widgets implement the ``__pt_container__`` method, which makes it possible to embed these in the layout like any other container. """ from __future__ import unicode_literals from .base import ( Box, Button, Checkbox, Frame, HorizontalLine, Label, ProgressBar, RadioList, Shadow, TextArea, VerticalLine, ) from .dialogs import Dialog from .menus import MenuContainer, MenuItem from .toolbars import ( ArgToolbar, CompletionsToolbar, FormattedTextToolbar, SearchToolbar, SystemToolbar, ValidationToolbar, ) __all__ = [ # Base. 'TextArea', 'Label', 'Button', 'Frame', 'Shadow', 'Box', 'VerticalLine', 'HorizontalLine', 'RadioList', 'Checkbox', 'ProgressBar', # Toolbars. 'ArgToolbar', 'CompletionsToolbar', 'FormattedTextToolbar', 'SearchToolbar', 'SystemToolbar', 'ValidationToolbar', # Dialogs. 'Dialog', # Menus. 'MenuContainer', 'MenuItem', ] prompt_toolkit-2.0.10/prompt_toolkit/widgets/base.py0000644000175100017510000006054513545407204024346 0ustar jonathanjonathan00000000000000""" Collection of reusable components for building full screen applications. All of these widgets implement the ``__pt_container__`` method, which makes them usable in any situation where we are expecting a `prompt_toolkit` container object. .. warning:: At this point, the API for these widgets is considered unstable, and can potentially change between minor releases (we try not too, but no guarantees are made yet). The public API in `prompt_toolkit.shortcuts.dialogs` on the other hand is considered stable. """ from __future__ import unicode_literals from functools import partial import six from prompt_toolkit.application.current import get_app from prompt_toolkit.auto_suggest import DynamicAutoSuggest from prompt_toolkit.buffer import Buffer from prompt_toolkit.completion import DynamicCompleter from prompt_toolkit.document import Document from prompt_toolkit.filters import ( Condition, has_focus, is_done, is_true, to_filter, ) from prompt_toolkit.formatted_text import ( Template, is_formatted_text, to_formatted_text, ) from prompt_toolkit.formatted_text.utils import fragment_list_to_text from prompt_toolkit.key_binding.key_bindings import KeyBindings from prompt_toolkit.keys import Keys from prompt_toolkit.layout.containers import ( ConditionalContainer, DynamicContainer, Float, FloatContainer, HSplit, VSplit, Window, WindowAlign, is_container, ) from prompt_toolkit.layout.controls import BufferControl, FormattedTextControl from prompt_toolkit.layout.dimension import Dimension as D from prompt_toolkit.layout.dimension import is_dimension, to_dimension from prompt_toolkit.layout.margins import NumberedMargin, ScrollbarMargin from prompt_toolkit.layout.processors import ( AppendAutoSuggestion, BeforeInput, ConditionalProcessor, PasswordProcessor, ) from prompt_toolkit.lexers import DynamicLexer from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.utils import get_cwidth from .toolbars import SearchToolbar __all__ = [ 'TextArea', 'Label', 'Button', 'Frame', 'Shadow', 'Box', 'VerticalLine', 'HorizontalLine', 'RadioList', 'Checkbox', # XXX: refactor into CheckboxList. 'ProgressBar', ] class Border: " Box drawing characters. (Thin) " HORIZONTAL = '\u2500' VERTICAL = '\u2502' TOP_LEFT = '\u250c' TOP_RIGHT = '\u2510' BOTTOM_LEFT = '\u2514' BOTTOM_RIGHT = '\u2518' class TextArea(object): """ A simple input field. This is a higher level abstraction on top of several other classes with sane defaults. This widget does have the most common options, but it does not intend to cover every single use case. For more configurations options, you can always build a text area manually, using a :class:`~prompt_toolkit.buffer.Buffer`, :class:`~prompt_toolkit.layout.BufferControl` and :class:`~prompt_toolkit.layout.Window`. Buffer attributes: :param text: The initial text. :param multiline: If True, allow multiline input. :param completer: :class:`~prompt_toolkit.completion.Completer` instance for auto completion. :param complete_while_typing: Boolean. :param accept_handler: Called when `Enter` is pressed (This should be a callable that takes a buffer as input). :param history: :class:`~prompt_toolkit.history.History` instance. :param auto_suggest: :class:`~prompt_toolkit.auto_suggest.AutoSuggest` instance for input suggestions. BufferControl attributes: :param password: When `True`, display using asterisks. :param focusable: When `True`, allow this widget to receive the focus. :param focus_on_click: When `True`, focus after mouse click. :param input_processors: `None` or a list of :class:`~prompt_toolkit.layout.Processor` objects. Window attributes: :param lexer: :class:`~prompt_toolkit.lexers.Lexer` instance for syntax highlighting. :param wrap_lines: When `True`, don't scroll horizontally, but wrap lines. :param width: Window width. (:class:`~prompt_toolkit.layout.Dimension` object.) :param height: Window height. (:class:`~prompt_toolkit.layout.Dimension` object.) :param scrollbar: When `True`, display a scroll bar. :param style: A style string. :param dont_extend_width: When `True`, don't take up more width then the preferred width reported by the control. :param dont_extend_height: When `True`, don't take up more width then the preferred height reported by the control. :param get_line_prefix: None or a callable that returns formatted text to be inserted before a line. It takes a line number (int) and a wrap_count and returns formatted text. This can be used for implementation of line continuations, things like Vim "breakindent" and so on. Other attributes: :param search_field: An optional `SearchToolbar` object. """ def __init__(self, text='', multiline=True, password=False, lexer=None, auto_suggest=None, completer=None, complete_while_typing=True, accept_handler=None, history=None, focusable=True, focus_on_click=False, wrap_lines=True, read_only=False, width=None, height=None, dont_extend_height=False, dont_extend_width=False, line_numbers=False, get_line_prefix=None, scrollbar=False, style='', search_field=None, preview_search=True, prompt='', input_processors=None): assert isinstance(text, six.text_type) assert search_field is None or isinstance(search_field, SearchToolbar) if search_field is None: search_control = None elif isinstance(search_field, SearchToolbar): search_control = search_field.control if input_processors is None: input_processors = [] # Writeable attributes. self.completer = completer self.complete_while_typing = complete_while_typing self.lexer = lexer self.auto_suggest = auto_suggest self.read_only = read_only self.wrap_lines = wrap_lines self.buffer = Buffer( document=Document(text, 0), multiline=multiline, read_only=Condition(lambda: is_true(self.read_only)), completer=DynamicCompleter(lambda: self.completer), complete_while_typing=Condition( lambda: is_true(self.complete_while_typing)), auto_suggest=DynamicAutoSuggest(lambda: self.auto_suggest), accept_handler=accept_handler, history=history) self.control = BufferControl( buffer=self.buffer, lexer=DynamicLexer(lambda: self.lexer), input_processors=[ ConditionalProcessor( AppendAutoSuggestion(), has_focus(self.buffer) & ~is_done), ConditionalProcessor( processor=PasswordProcessor(), filter=to_filter(password) ), BeforeInput(prompt, style='class:text-area.prompt'), ] + input_processors, search_buffer_control=search_control, preview_search=preview_search, focusable=focusable, focus_on_click=focus_on_click) if multiline: if scrollbar: right_margins = [ScrollbarMargin(display_arrows=True)] else: right_margins = [] if line_numbers: left_margins = [NumberedMargin()] else: left_margins = [] else: height = D.exact(1) left_margins = [] right_margins = [] style = 'class:text-area ' + style self.window = Window( height=height, width=width, dont_extend_height=dont_extend_height, dont_extend_width=dont_extend_width, content=self.control, style=style, wrap_lines=Condition(lambda: is_true(self.wrap_lines)), left_margins=left_margins, right_margins=right_margins, get_line_prefix=get_line_prefix) @property def text(self): """ The `Buffer` text. """ return self.buffer.text @text.setter def text(self, value): self.buffer.set_document(Document(value, 0), bypass_readonly=True) @property def document(self): """ The `Buffer` document (text + cursor position). """ return self.buffer.document @document.setter def document(self, value): self.buffer.document = value @property def accept_handler(self): """ The accept handler. Called when the user accepts the input. """ return self.buffer.accept_handler @accept_handler.setter def accept_handler(self, value): self.buffer.accept_handler = value def __pt_container__(self): return self.window class Label(object): """ Widget that displays the given text. It is not editable or focusable. :param text: The text to be displayed. (This can be multiline. This can be formatted text as well.) :param style: A style string. :param width: When given, use this width, rather than calculating it from the text size. """ def __init__(self, text, style='', width=None, dont_extend_height=True, dont_extend_width=False): assert is_formatted_text(text) self.text = text def get_width(): if width is None: text_fragments = to_formatted_text(self.text) text = fragment_list_to_text(text_fragments) if text: longest_line = max(get_cwidth(line) for line in text.splitlines()) else: return D(preferred=0) return D(preferred=longest_line) else: return width self.formatted_text_control = FormattedTextControl( text=lambda: self.text) self.window = Window( content=self.formatted_text_control, width=get_width, style='class:label ' + style, dont_extend_height=dont_extend_height, dont_extend_width=dont_extend_width) def __pt_container__(self): return self.window class Button(object): """ Clickable button. :param text: The caption for the button. :param handler: `None` or callable. Called when the button is clicked. :param width: Width of the button. """ def __init__(self, text, handler=None, width=12): assert isinstance(text, six.text_type) assert handler is None or callable(handler) assert isinstance(width, int) self.text = text self.handler = handler self.width = width self.control = FormattedTextControl( self._get_text_fragments, key_bindings=self._get_key_bindings(), focusable=True) def get_style(): if get_app().layout.has_focus(self): return 'class:button.focused' else: return 'class:button' self.window = Window( self.control, align=WindowAlign.CENTER, height=1, width=width, style=get_style, dont_extend_width=True, dont_extend_height=True) def _get_text_fragments(self): text = ('{:^%s}' % (self.width - 2)).format(self.text) def handler(mouse_event): if mouse_event.event_type == MouseEventType.MOUSE_UP: self.handler() return [ ('class:button.arrow', '<', handler), ('[SetCursorPosition]', ''), ('class:button.text', text, handler), ('class:button.arrow', '>', handler), ] def _get_key_bindings(self): " Key bindings for the Button. " kb = KeyBindings() @kb.add(' ') @kb.add('enter') def _(event): if self.handler is not None: self.handler() return kb def __pt_container__(self): return self.window class Frame(object): """ Draw a border around any container, optionally with a title text. Changing the title and body of the frame is possible at runtime by assigning to the `body` and `title` attributes of this class. :param body: Another container object. :param title: Text to be displayed in the top of the frame (can be formatted text). :param style: Style string to be applied to this widget. """ def __init__(self, body, title='', style='', width=None, height=None, key_bindings=None, modal=False): assert is_container(body) assert is_formatted_text(title) assert isinstance(style, six.text_type) assert is_dimension(width) assert is_dimension(height) assert key_bindings is None or isinstance(key_bindings, KeyBindings) assert isinstance(modal, bool) self.title = title self.body = body fill = partial(Window, style='class:frame.border') style = 'class:frame ' + style top_row_with_title = VSplit([ fill(width=1, height=1, char=Border.TOP_LEFT), fill(char=Border.HORIZONTAL), fill(width=1, height=1, char='|'), # Notice: we use `Template` here, because `self.title` can be an # `HTML` object for instance. Label(lambda: Template(' {} ').format(self.title), style='class:frame.label', dont_extend_width=True), fill(width=1, height=1, char='|'), fill(char=Border.HORIZONTAL), fill(width=1, height=1, char=Border.TOP_RIGHT), ], height=1) top_row_without_title = VSplit([ fill(width=1, height=1, char=Border.TOP_LEFT), fill(char=Border.HORIZONTAL), fill(width=1, height=1, char=Border.TOP_RIGHT), ], height=1) @Condition def has_title(): return bool(self.title) self.container = HSplit([ ConditionalContainer( content=top_row_with_title, filter=has_title), ConditionalContainer( content=top_row_without_title, filter=~has_title), VSplit([ fill(width=1, char=Border.VERTICAL), DynamicContainer(lambda: self.body), fill(width=1, char=Border.VERTICAL), # Padding is required to make sure that if the content is # too small, the right frame border is still aligned. ], padding=0), VSplit([ fill(width=1, height=1, char=Border.BOTTOM_LEFT), fill(char=Border.HORIZONTAL), fill(width=1, height=1, char=Border.BOTTOM_RIGHT), ]), ], width=width, height=height, style=style, key_bindings=key_bindings, modal=modal) def __pt_container__(self): return self.container class Shadow(object): """ Draw a shadow underneath/behind this container. (This applies `class:shadow` the the cells under the shadow. The Style should define the colors for the shadow.) :param body: Another container object. """ def __init__(self, body): assert is_container(body) self.container = FloatContainer( content=body, floats=[ Float(bottom=-1, height=1, left=1, right=-1, transparent=True, content=Window(style='class:shadow')), Float(bottom=-1, top=1, width=1, right=-1, transparent=True, content=Window(style='class:shadow')), ] ) def __pt_container__(self): return self.container class Box(object): """ Add padding around a container. This also makes sure that the parent can provide more space than required by the child. This is very useful when wrapping a small element with a fixed size into a ``VSplit`` or ``HSplit`` object. The ``HSplit`` and ``VSplit`` try to make sure to adapt respectively the width and height, possibly shrinking other elements. Wrapping something in a ``Box`` makes it flexible. :param body: Another container object. :param padding: The margin to be used around the body. This can be overridden by `padding_left`, padding_right`, `padding_top` and `padding_bottom`. :param style: A style string. :param char: Character to be used for filling the space around the body. (This is supposed to be a character with a terminal width of 1.) """ def __init__(self, body, padding=None, padding_left=None, padding_right=None, padding_top=None, padding_bottom=None, width=None, height=None, style='', char=None, modal=False, key_bindings=None): assert is_container(body) if padding is None: padding = D(preferred=0) def get(value): if value is None: value = padding return to_dimension(value) self.padding_left = get(padding_left) self.padding_right = get(padding_right) self.padding_top = get(padding_top) self.padding_bottom = get(padding_bottom) self.body = body self.container = HSplit([ Window(height=self.padding_top, char=char), VSplit([ Window(width=self.padding_left, char=char), body, Window(width=self.padding_right, char=char), ]), Window(height=self.padding_bottom, char=char), ], width=width, height=height, style=style, modal=modal, key_bindings=None) def __pt_container__(self): return self.container class Checkbox(object): def __init__(self, text=''): assert is_formatted_text(text) self.checked = True kb = KeyBindings() @kb.add(' ') @kb.add('enter') def _(event): self.checked = not self.checked self.control = FormattedTextControl( self._get_text_fragments, key_bindings=kb, focusable=True) self.window = Window( width=3, content=self.control, height=1) self.container = VSplit([ self.window, Label(text=Template(' {}').format(text)) ], style='class:checkbox') def _get_text_fragments(self): result = [('', '[')] result.append(('[SetCursorPosition]', '')) if self.checked: result.append(('', '*')) else: result.append(('', ' ')) result.append(('', ']')) return result def __pt_container__(self): return self.container class RadioList(object): """ List of radio buttons. Only one can be checked at the same time. :param values: List of (value, label) tuples. """ def __init__(self, values): assert isinstance(values, list) assert len(values) > 0 assert all(isinstance(i, tuple) and len(i) == 2 for i in values) self.values = values self.current_value = values[0][0] self._selected_index = 0 # Key bindings. kb = KeyBindings() @kb.add('up') def _(event): self._selected_index = max(0, self._selected_index - 1) @kb.add('down') def _(event): self._selected_index = min( len(self.values) - 1, self._selected_index + 1) @kb.add('pageup') def _(event): w = event.app.layout.current_window self._selected_index = max( 0, self._selected_index - len(w.render_info.displayed_lines) ) @kb.add('pagedown') def _(event): w = event.app.layout.current_window self._selected_index = min( len(self.values) - 1, self._selected_index + len(w.render_info.displayed_lines) ) @kb.add('enter') @kb.add(' ') def _(event): self.current_value = self.values[self._selected_index][0] @kb.add(Keys.Any) def _(event): # We first check values after the selected value, then all values. for value in self.values[self._selected_index + 1:] + self.values: if value[1].startswith(event.data): self._selected_index = self.values.index(value) return # Control and window. self.control = FormattedTextControl( self._get_text_fragments, key_bindings=kb, focusable=True) self.window = Window( content=self.control, style='class:radio-list', right_margins=[ ScrollbarMargin(display_arrows=True), ], dont_extend_height=True) def _get_text_fragments(self): def mouse_handler(mouse_event): """ Set `_selected_index` and `current_value` according to the y position of the mouse click event. """ if mouse_event.event_type == MouseEventType.MOUSE_UP: self._selected_index = mouse_event.position.y self.current_value = self.values[self._selected_index][0] result = [] for i, value in enumerate(self.values): checked = (value[0] == self.current_value) selected = (i == self._selected_index) style = '' if checked: style += ' class:radio-checked' if selected: style += ' class:radio-selected' result.append((style, '(')) if selected: result.append(('[SetCursorPosition]', '')) if checked: result.append((style, '*')) else: result.append((style, ' ')) result.append((style, ')')) result.append(('class:radio', ' ')) result.extend(to_formatted_text(value[1], style='class:radio')) result.append(('', '\n')) # Add mouse handler to all fragments. for i in range(len(result)): result[i] = (result[i][0], result[i][1], mouse_handler) result.pop() # Remove last newline. return result def __pt_container__(self): return self.window class VerticalLine(object): """ A simple vertical line with a width of 1. """ def __init__(self): self.window = Window( char=Border.VERTICAL, style='class:line,vertical-line', width=1) def __pt_container__(self): return self.window class HorizontalLine(object): """ A simple horizontal line with a height of 1. """ def __init__(self): self.window = Window( char=Border.HORIZONTAL, style='class:line,horizontal-line', height=1) def __pt_container__(self): return self.window class ProgressBar(object): def __init__(self): self._percentage = 60 self.label = Label('60%') self.container = FloatContainer( content=Window(height=1), floats=[ # We first draw the label, then the actual progress bar. Right # now, this is the only way to have the colors of the progress # bar appear on top of the label. The problem is that our label # can't be part of any `Window` below. Float(content=self.label, top=0, bottom=0), Float(left=0, top=0, right=0, bottom=0, content=VSplit([ Window(style='class:progress-bar.used', width=lambda: D(weight=int(self._percentage))), Window(style='class:progress-bar', width=lambda: D(weight=int(100 - self._percentage))), ])), ]) @property def percentage(self): return self._percentage @percentage.setter def percentage(self, value): assert isinstance(value, int) self._percentage = value self.label.text = '{0}%'.format(value) def __pt_container__(self): return self.container prompt_toolkit-2.0.10/prompt_toolkit/widgets/dialogs.py0000644000175100017510000000565213545407204025054 0ustar jonathanjonathan00000000000000""" Collection of reusable components for building full screen applications. """ from __future__ import unicode_literals from prompt_toolkit.filters import has_completions, has_focus from prompt_toolkit.formatted_text import is_formatted_text from prompt_toolkit.key_binding.bindings.focus import ( focus_next, focus_previous, ) from prompt_toolkit.key_binding.key_bindings import KeyBindings from prompt_toolkit.layout.containers import DynamicContainer, HSplit, VSplit from prompt_toolkit.layout.dimension import Dimension as D from .base import Box, Frame, Shadow __all__ = [ 'Dialog', ] class Dialog(object): """ Simple dialog window. This is the base for input dialogs, message dialogs and confirmation dialogs. Changing the title and body of the dialog is possible at runtime by assigning to the `body` and `title` attributes of this class. :param body: Child container object. :param title: Text to be displayed in the heading of the dialog. :param buttons: A list of `Button` widgets, displayed at the bottom. """ def __init__(self, body, title='', buttons=None, modal=True, width=None, with_background=False): assert is_formatted_text(title) assert buttons is None or isinstance(buttons, list) self.body = body self.title = title buttons = buttons or [] # When a button is selected, handle left/right key bindings. buttons_kb = KeyBindings() if len(buttons) > 1: first_selected = has_focus(buttons[0]) last_selected = has_focus(buttons[-1]) buttons_kb.add('left', filter=~first_selected)(focus_previous) buttons_kb.add('right', filter=~last_selected)(focus_next) if buttons: frame_body = HSplit([ # Add optional padding around the body. Box(body=DynamicContainer(lambda: self.body), padding=D(preferred=1, max=1), padding_bottom=0), # The buttons. Box(body=VSplit(buttons, padding=1, key_bindings=buttons_kb), height=D(min=1, max=3, preferred=3)) ]) else: frame_body = body # Key bindings for whole dialog. kb = KeyBindings() kb.add('tab', filter=~has_completions)(focus_next) kb.add('s-tab', filter=~has_completions)(focus_previous) frame = Shadow(body=Frame( title=lambda: self.title, body=frame_body, style='class:dialog.body', width=(None if with_background is None else width), key_bindings=kb, modal=modal, )) if with_background: self.container = Box( body=frame, style='class:dialog', width=width) else: self.container = frame def __pt_container__(self): return self.container prompt_toolkit-2.0.10/prompt_toolkit/widgets/menus.py0000644000175100017510000002577413545407204024570 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.application.current import get_app from prompt_toolkit.filters import Condition from prompt_toolkit.key_binding.key_bindings import KeyBindings from prompt_toolkit.layout.containers import ( ConditionalContainer, Float, FloatContainer, HSplit, Window, ) from prompt_toolkit.layout.controls import FormattedTextControl from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.utils import get_cwidth from prompt_toolkit.widgets import Shadow from .base import Border __all__ = [ 'MenuContainer', 'MenuItem', ] class MenuContainer(object): """ :param floats: List of extra Float objects to display. :param menu_items: List of `MenuItem` objects. """ def __init__(self, body, menu_items=None, floats=None, key_bindings=None): assert isinstance(menu_items, list) and \ all(isinstance(i, MenuItem) for i in menu_items) assert floats is None or all(isinstance(f, Float) for f in floats) self.body = body self.menu_items = menu_items self.selected_menu = [0] # Key bindings. kb = KeyBindings() @Condition def in_main_menu(): return len(self.selected_menu) == 1 @Condition def in_sub_menu(): return len(self.selected_menu) > 1 # Navigation through the main menu. @kb.add('left', filter=in_main_menu) def _(event): self.selected_menu[0] = max(0, self.selected_menu[0] - 1) @kb.add('right', filter=in_main_menu) def _(event): self.selected_menu[0] = min( len(self.menu_items) - 1, self.selected_menu[0] + 1) @kb.add('down', filter=in_main_menu) def _(event): self.selected_menu.append(0) @kb.add('c-c', filter=in_main_menu) @kb.add('c-g', filter=in_main_menu) def _(event): " Leave menu. " event.app.layout.focus_last() # Sub menu navigation. @kb.add('left', filter=in_sub_menu) @kb.add('c-g', filter=in_sub_menu) @kb.add('c-c', filter=in_sub_menu) def _(event): " Go back to parent menu. " if len(self.selected_menu) > 1: self.selected_menu.pop() @kb.add('right', filter=in_sub_menu) def _(event): " go into sub menu. " if self._get_menu(len(self.selected_menu) - 1).children: self.selected_menu.append(0) # If This item does not have a sub menu. Go up in the parent menu. elif len(self.selected_menu) == 2 and self.selected_menu[0] < len(self.menu_items) - 1: self.selected_menu = [min( len(self.menu_items) - 1, self.selected_menu[0] + 1)] if self.menu_items[self.selected_menu[0]].children: self.selected_menu.append(0) @kb.add('up', filter=in_sub_menu) def _(event): " Select previous (enabled) menu item or return to main menu. " # Look for previous enabled items in this sub menu. menu = self._get_menu(len(self.selected_menu) - 2) index = self.selected_menu[-1] previous_indexes = [i for i, item in enumerate(menu.children) if i < index and not item.disabled] if previous_indexes: self.selected_menu[-1] = previous_indexes[-1] elif len(self.selected_menu) == 2: # Return to main menu. self.selected_menu.pop() @kb.add('down', filter=in_sub_menu) def _(event): " Select next (enabled) menu item. " menu = self._get_menu(len(self.selected_menu) - 2) index = self.selected_menu[-1] next_indexes = [i for i, item in enumerate(menu.children) if i > index and not item.disabled] if next_indexes: self.selected_menu[-1] = next_indexes[0] @kb.add('enter') def _(event): " Click the selected menu item. " item = self._get_menu(len(self.selected_menu) - 1) if item.handler: event.app.layout.focus_last() item.handler() # Controls. self.control = FormattedTextControl( self._get_menu_fragments, key_bindings=kb, focusable=True, show_cursor=False) self.window = Window( height=1, content=self.control, style='class:menu-bar') submenu = self._submenu(0) submenu2 = self._submenu(1) submenu3 = self._submenu(2) @Condition def has_focus(): return get_app().layout.current_window == self.window self.container = FloatContainer( content=HSplit([ # The titlebar. self.window, # The 'body', like defined above. body, ]), floats=[ Float(xcursor=self.window, ycursor=self.window, content=ConditionalContainer( content=Shadow(body=submenu), filter=has_focus)), Float(attach_to_window=submenu, xcursor=True, ycursor=True, allow_cover_cursor=True, content=ConditionalContainer( content=Shadow(body=submenu2), filter=has_focus & Condition(lambda: len(self.selected_menu) >= 1))), Float(attach_to_window=submenu2, xcursor=True, ycursor=True, allow_cover_cursor=True, content=ConditionalContainer( content=Shadow(body=submenu3), filter=has_focus & Condition(lambda: len(self.selected_menu) >= 2))), # -- ] + (floats or []), key_bindings=key_bindings, ) def _get_menu(self, level): menu = self.menu_items[self.selected_menu[0]] for i, index in enumerate(self.selected_menu[1:]): if i < level: try: menu = menu.children[index] except IndexError: return MenuItem('debug') return menu def _get_menu_fragments(self): focused = get_app().layout.has_focus(self.window) # This is called during the rendering. When we discover that this # widget doesn't have the focus anymore. Reset menu state. if not focused: self.selected_menu = [0] # Generate text fragments for the main menu. def one_item(i, item): def mouse_handler(mouse_event): if mouse_event.event_type == MouseEventType.MOUSE_UP: # Toggle focus. app = get_app() if app.layout.has_focus(self.window): if self.selected_menu == [i]: app.layout.focus_last() else: app.layout.focus(self.window) self.selected_menu = [i] yield ('class:menu-bar', ' ', mouse_handler) if i == self.selected_menu[0] and focused: yield ('[SetMenuPosition]', '', mouse_handler) style = 'class:menu-bar.selected-item' else: style = 'class:menu-bar' yield style, item.text, mouse_handler result = [] for i, item in enumerate(self.menu_items): result.extend(one_item(i, item)) return result def _submenu(self, level=0): def get_text_fragments(): result = [] if level < len(self.selected_menu): menu = self._get_menu(level) if menu.children: result.append(('class:menu', Border.TOP_LEFT)) result.append(('class:menu', Border.HORIZONTAL * (menu.width + 4))) result.append(('class:menu', Border.TOP_RIGHT)) result.append(('', '\n')) try: selected_item = self.selected_menu[level + 1] except IndexError: selected_item = -1 def one_item(i, item): def mouse_handler(mouse_event): if mouse_event.event_type == MouseEventType.MOUSE_UP: app = get_app() if item.handler: app.layout.focus_last() item.handler() else: self.selected_menu = self.selected_menu[:level + 1] + [i] if i == selected_item: yield ('[SetCursorPosition]', '') style = 'class:menu-bar.selected-item' else: style = '' yield ('class:menu', Border.VERTICAL) if item.text == '-': yield (style + 'class:menu-border', '{}'.format(Border.HORIZONTAL * (menu.width + 3)), mouse_handler) else: yield (style, ' {}'.format(item.text).ljust(menu.width + 3), mouse_handler) if item.children: yield (style, '>', mouse_handler) else: yield (style, ' ', mouse_handler) if i == selected_item: yield ('[SetMenuPosition]', '') yield ('class:menu', Border.VERTICAL) yield ('', '\n') for i, item in enumerate(menu.children): result.extend(one_item(i, item)) result.append(('class:menu', Border.BOTTOM_LEFT)) result.append(('class:menu', Border.HORIZONTAL * (menu.width + 4))) result.append(('class:menu', Border.BOTTOM_RIGHT)) return result return Window( FormattedTextControl(get_text_fragments), style='class:menu') @property def floats(self): return self.container.floats def __pt_container__(self): return self.container class MenuItem(object): def __init__(self, text='', handler=None, children=None, shortcut=None, disabled=False): self.text = text self.handler = handler self.children = children or [] self.shortcut = shortcut self.disabled = disabled self.selected_item = 0 @property def width(self): if self.children: return max(get_cwidth(c.text) for c in self.children) else: return 0 prompt_toolkit-2.0.10/prompt_toolkit/widgets/toolbars.py0000644000175100017510000002567413545407204025265 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.application.current import get_app from prompt_toolkit.buffer import Buffer from prompt_toolkit.enums import SYSTEM_BUFFER from prompt_toolkit.filters import ( Condition, emacs_mode, has_arg, has_completions, has_focus, has_validation_error, to_filter, vi_mode, vi_navigation_mode, ) from prompt_toolkit.formatted_text import fragment_list_len, to_formatted_text from prompt_toolkit.key_binding.key_bindings import ( ConditionalKeyBindings, KeyBindings, merge_key_bindings, ) from prompt_toolkit.key_binding.vi_state import InputMode from prompt_toolkit.keys import Keys from prompt_toolkit.layout.containers import ConditionalContainer, Window from prompt_toolkit.layout.controls import ( BufferControl, FormattedTextControl, SearchBufferControl, UIContent, UIControl, ) from prompt_toolkit.layout.dimension import Dimension from prompt_toolkit.layout.processors import BeforeInput from prompt_toolkit.lexers import SimpleLexer from prompt_toolkit.search import SearchDirection __all__ = [ 'ArgToolbar', 'CompletionsToolbar', 'FormattedTextToolbar', 'SearchToolbar', 'SystemToolbar', 'ValidationToolbar', ] class FormattedTextToolbar(Window): def __init__(self, text, **kw): # The style needs to be applied to the toolbar as a whole, not just the # `FormattedTextControl`. style = kw.pop('style', '') super(FormattedTextToolbar, self).__init__( FormattedTextControl(text, **kw), style=style, dont_extend_height=True, height=Dimension(min=1)) class SystemToolbar(object): """ Toolbar for a system prompt. :param prompt: Prompt to be displayed to the user. """ def __init__(self, prompt='Shell command: ', enable_global_bindings=True): self.prompt = prompt self.enable_global_bindings = to_filter(enable_global_bindings) self.system_buffer = Buffer(name=SYSTEM_BUFFER) self._bindings = self._build_key_bindings() self.buffer_control = BufferControl( buffer=self.system_buffer, lexer=SimpleLexer(style='class:system-toolbar.text'), input_processors=[BeforeInput( lambda: self.prompt, style='class:system-toolbar')], key_bindings=self._bindings) self.window = Window( self.buffer_control, height=1, style='class:system-toolbar') self.container = ConditionalContainer( content=self.window, filter=has_focus(self.system_buffer)) def _get_display_before_text(self): return [ ('class:system-toolbar', 'Shell command: '), ('class:system-toolbar.text', self.system_buffer.text), ('', '\n'), ] def _build_key_bindings(self): focused = has_focus(self.system_buffer) # Emacs emacs_bindings = KeyBindings() handle = emacs_bindings.add @handle('escape', filter=focused) @handle('c-g', filter=focused) @handle('c-c', filter=focused) def _(event): " Hide system prompt. " self.system_buffer.reset() event.app.layout.focus_last() @handle('enter', filter=focused) def _(event): " Run system command. " event.app.run_system_command( self.system_buffer.text, display_before_text=self._get_display_before_text()) self.system_buffer.reset(append_to_history=True) event.app.layout.focus_last() # Vi. vi_bindings = KeyBindings() handle = vi_bindings.add @handle('escape', filter=focused) @handle('c-c', filter=focused) def _(event): " Hide system prompt. " event.app.vi_state.input_mode = InputMode.NAVIGATION self.system_buffer.reset() event.app.layout.focus_last() @handle('enter', filter=focused) def _(event): " Run system command. " event.app.vi_state.input_mode = InputMode.NAVIGATION event.app.run_system_command( self.system_buffer.text, display_before_text=self._get_display_before_text()) self.system_buffer.reset(append_to_history=True) event.app.layout.focus_last() # Global bindings. (Listen to these bindings, even when this widget is # not focussed.) global_bindings = KeyBindings() handle = global_bindings.add @handle(Keys.Escape, '!', filter= ~focused & emacs_mode, is_global=True) def _(event): " M-'!' will focus this user control. " event.app.layout.focus(self.window) @handle('!', filter=~focused & vi_mode & vi_navigation_mode, is_global=True) def _(event): " Focus. " event.app.vi_state.input_mode = InputMode.INSERT event.app.layout.focus(self.window) return merge_key_bindings([ ConditionalKeyBindings(emacs_bindings, emacs_mode), ConditionalKeyBindings(vi_bindings, vi_mode), ConditionalKeyBindings(global_bindings, self.enable_global_bindings), ]) def __pt_container__(self): return self.container class ArgToolbar(object): def __init__(self): def get_formatted_text(): arg = get_app().key_processor.arg or '' if arg == '-': arg = '-1' return [ ('class:arg-toolbar', 'Repeat: '), ('class:arg-toolbar.text', arg), ] self.window = Window( FormattedTextControl(get_formatted_text), height=1) self.container = ConditionalContainer( content=self.window, filter=has_arg) def __pt_container__(self): return self.container class SearchToolbar(object): """ :param vi_mode: Display '/' and '?' instead of I-search. :param ignore_case: Search case insensitive. """ def __init__(self, search_buffer=None, vi_mode=False, text_if_not_searching='', forward_search_prompt='I-search: ', backward_search_prompt='I-search backward: ', ignore_case=False): assert search_buffer is None or isinstance(search_buffer, Buffer) if search_buffer is None: search_buffer = Buffer() @Condition def is_searching(): return self.control in get_app().layout.search_links def get_before_input(): if not is_searching(): return text_if_not_searching elif self.control.searcher_search_state.direction == SearchDirection.BACKWARD: return ('?' if vi_mode else backward_search_prompt) else: return ('/' if vi_mode else forward_search_prompt) self.search_buffer = search_buffer self.control = SearchBufferControl( buffer=search_buffer, input_processors=[BeforeInput( get_before_input, style='class:search-toolbar.prompt')], lexer=SimpleLexer( style='class:search-toolbar.text'), ignore_case=ignore_case) self.container = ConditionalContainer( content=Window( self.control, height=1, style='class:search-toolbar'), filter=is_searching) def __pt_container__(self): return self.container class _CompletionsToolbarControl(UIControl): def create_content(self, width, height): complete_state = get_app().current_buffer.complete_state if complete_state: completions = complete_state.completions index = complete_state.complete_index # Can be None! # Width of the completions without the left/right arrows in the margins. content_width = width - 6 # Booleans indicating whether we stripped from the left/right cut_left = False cut_right = False # Create Menu content. fragments = [] for i, c in enumerate(completions): # When there is no more place for the next completion if fragment_list_len(fragments) + len(c.display_text) >= content_width: # If the current one was not yet displayed, page to the next sequence. if i <= (index or 0): fragments = [] cut_left = True # If the current one is visible, stop here. else: cut_right = True break fragments.extend(to_formatted_text( c.display_text, style=('class:completion-toolbar.completion.current' if i == index else 'class:completion-toolbar.completion') )) fragments.append(('', ' ')) # Extend/strip until the content width. fragments.append(('', ' ' * (content_width - fragment_list_len(fragments)))) fragments = fragments[:content_width] # Return fragments all_fragments = [ ('', ' '), ('class:completion-toolbar.arrow', '<' if cut_left else ' '), ('', ' '), ] + fragments + [ ('', ' '), ('class:completion-toolbar.arrow', '>' if cut_right else ' '), ('', ' '), ] else: all_fragments = [] def get_line(i): return all_fragments return UIContent(get_line=get_line, line_count=1) class CompletionsToolbar(object): def __init__(self): self.container = ConditionalContainer( content=Window( _CompletionsToolbarControl(), height=1, style='class:completion-toolbar'), filter=has_completions) def __pt_container__(self): return self.container class ValidationToolbar(object): def __init__(self, show_position=False): def get_formatted_text(): buff = get_app().current_buffer if buff.validation_error: row, column = buff.document.translate_index_to_position( buff.validation_error.cursor_position) if show_position: text = '%s (line=%s column=%s)' % ( buff.validation_error.message, row + 1, column + 1) else: text = buff.validation_error.message return [('class:validation-toolbar', text)] else: return [] self.control = FormattedTextControl(get_formatted_text) self.container = ConditionalContainer( content=Window(self.control, height=1), filter=has_validation_error) def __pt_container__(self): return self.container prompt_toolkit-2.0.10/prompt_toolkit/win32_types.py0000644000175100017510000000771513545407204024154 0ustar jonathanjonathan00000000000000from ctypes import Structure, Union, c_char, c_long, c_short, c_ulong from ctypes.wintypes import BOOL, DWORD, LPVOID, WCHAR, WORD # Input/Output standard device numbers. Note that these are not handle objects. # It's the `windll.kernel32.GetStdHandle` system call that turns them into a # real handle object. STD_INPUT_HANDLE = c_ulong(-10) STD_OUTPUT_HANDLE = c_ulong(-11) STD_ERROR_HANDLE = c_ulong(-12) class COORD(Structure): """ Struct in wincon.h http://msdn.microsoft.com/en-us/library/windows/desktop/ms682119(v=vs.85).aspx """ _fields_ = [ ('X', c_short), # Short ('Y', c_short), # Short ] def __repr__(self): return '%s(X=%r, Y=%r, type_x=%r, type_y=%r)' % ( self.__class__.__name__, self.X, self.Y, type(self.X), type(self.Y)) class UNICODE_OR_ASCII(Union): _fields_ = [ ('AsciiChar', c_char), ('UnicodeChar', WCHAR), ] class KEY_EVENT_RECORD(Structure): """ http://msdn.microsoft.com/en-us/library/windows/desktop/ms684166(v=vs.85).aspx """ _fields_ = [ ('KeyDown', c_long), # bool ('RepeatCount', c_short), # word ('VirtualKeyCode', c_short), # word ('VirtualScanCode', c_short), # word ('uChar', UNICODE_OR_ASCII), # Unicode or ASCII. ('ControlKeyState', c_long) # double word ] class MOUSE_EVENT_RECORD(Structure): """ http://msdn.microsoft.com/en-us/library/windows/desktop/ms684239(v=vs.85).aspx """ _fields_ = [ ('MousePosition', COORD), ('ButtonState', c_long), # dword ('ControlKeyState', c_long), # dword ('EventFlags', c_long) # dword ] class WINDOW_BUFFER_SIZE_RECORD(Structure): """ http://msdn.microsoft.com/en-us/library/windows/desktop/ms687093(v=vs.85).aspx """ _fields_ = [ ('Size', COORD) ] class MENU_EVENT_RECORD(Structure): """ http://msdn.microsoft.com/en-us/library/windows/desktop/ms684213(v=vs.85).aspx """ _fields_ = [ ('CommandId', c_long) # uint ] class FOCUS_EVENT_RECORD(Structure): """ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683149(v=vs.85).aspx """ _fields_ = [ ('SetFocus', c_long) # bool ] class EVENT_RECORD(Union): _fields_ = [ ('KeyEvent', KEY_EVENT_RECORD), ('MouseEvent', MOUSE_EVENT_RECORD), ('WindowBufferSizeEvent', WINDOW_BUFFER_SIZE_RECORD), ('MenuEvent', MENU_EVENT_RECORD), ('FocusEvent', FOCUS_EVENT_RECORD) ] class INPUT_RECORD(Structure): """ http://msdn.microsoft.com/en-us/library/windows/desktop/ms683499(v=vs.85).aspx """ _fields_ = [ ('EventType', c_short), # word ('Event', EVENT_RECORD) # Union. ] EventTypes = { 1: 'KeyEvent', 2: 'MouseEvent', 4: 'WindowBufferSizeEvent', 8: 'MenuEvent', 16: 'FocusEvent' } class SMALL_RECT(Structure): """struct in wincon.h.""" _fields_ = [ ("Left", c_short), ("Top", c_short), ("Right", c_short), ("Bottom", c_short), ] class CONSOLE_SCREEN_BUFFER_INFO(Structure): """struct in wincon.h.""" _fields_ = [ ("dwSize", COORD), ("dwCursorPosition", COORD), ("wAttributes", WORD), ("srWindow", SMALL_RECT), ("dwMaximumWindowSize", COORD), ] def __str__(self): return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( self.dwSize.Y, self.dwSize.X, self.dwCursorPosition.Y, self.dwCursorPosition.X, self.wAttributes, self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right, self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X, ) class SECURITY_ATTRIBUTES(Structure): """ http://msdn.microsoft.com/en-us/library/windows/desktop/aa379560(v=vs.85).aspx """ _fields_ = [ ('nLength', DWORD), ('lpSecurityDescriptor', LPVOID), ('bInheritHandle', BOOL), ] prompt_toolkit-2.0.10/prompt_toolkit.egg-info/0000755000175100017510000000000013545410361023072 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/prompt_toolkit.egg-info/PKG-INFO0000644000175100017510000003164013545410360024172 0ustar jonathanjonathan00000000000000Metadata-Version: 1.2 Name: prompt-toolkit Version: 2.0.10 Summary: Library for building powerful interactive command lines in Python Home-page: https://github.com/jonathanslenders/python-prompt-toolkit Author: Jonathan Slenders License: BSD-3-Clause Description: Python Prompt Toolkit ===================== |Build Status| |AppVeyor| |PyPI| |RTD| |License| |Codecov| .. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png *``prompt_toolkit`` is a library for building powerful interactive command line applications in Python.* Read the `documentation on readthedocs `_. NOTICE: prompt_toolkit 2.0 ************************** Please notice that this is prompt_toolkit 2.0. It is incompatible with the 1.0 branch, but much better in many regards. Many applications are still using prompt_toolkit 1.0, but upgrading is strongly recommended. Feel free to open a new issue if you don't manage to upgrade to prompt_toolkit 2.0. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt_toolkit. .. image :: https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/ptpython.png prompt_toolkit features *********************** ``prompt_toolkit`` could be a replacement for `GNU readline `_, but it can be much more than that. Some features: - Pure Python. - Syntax highlighting of the input while typing. (For instance, with a Pygments lexer.) - Multi-line input editing. - Advanced code completion. - Both Emacs and Vi key bindings. (Similar to readline.) - Even some advanced Vi functionality, like named registers and digraphs. - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.7. - Works well with Unicode double width characters. (Chinese input.) - Selecting text for copy/paste. (Both Emacs and Vi style.) - Support for `bracketed paste `_. - Mouse support for cursor positioning and scrolling. - Auto suggestions. (Like `fish shell `_.) - Multiple input buffers. - No global state. - Lightweight, the only dependencies are Pygments, six and wcwidth. - Runs on Linux, OS X, FreeBSD, OpenBSD and Windows systems. - And much more... Feel free to create tickets for bugs and feature requests, and create pull requests if you have nice patches that you would like to share with others. About Windows support ********************* ``prompt_toolkit`` is cross platform, and everything that you build on top should run fine on both Unix and Windows systems. On Windows, it uses a different event loop (``WaitForMultipleObjects`` instead of ``select``), and another input and output system. (Win32 APIs instead of pseudo-terminals and VT100.) It's worth noting that the implementation is a "best effort of what is possible". Both Unix and Windows terminals have their limitations. But in general, the Unix experience will still be a little better. For Windows, it's recommended to use either `cmder `_ or `conemu `_. Installation ************ :: pip install prompt_toolkit For Conda, do: :: conda install -c https://conda.anaconda.org/conda-forge prompt_toolkit Getting started *************** The most simple example of the library would look like this: .. code:: python from prompt_toolkit import prompt if __name__ == '__main__': answer = prompt('Give me some input: ') print('You said: %s' % answer) For more complex examples, have a look in the ``examples`` directory. All examples are chosen to demonstrate only one thing. Also, don't be afraid to look at the source code. The implementation of the ``prompt`` function could be a good start. Note for Python 2: all strings are expected to be unicode strings. So, either put a small ``u`` in front of every string or put ``from __future__ import unicode_literals`` at the start of the above example. Projects using prompt_toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql client. - `litecli `_: SQLite client. - `wharfee `_: A Docker command line. - `xonsh `_: A Python-ish, BASHwards-compatible shell. - `saws `_: A Supercharged AWS Command Line Interface. - `cycli `_: A Command Line Interface for Cypher. - `crash `_: Crate command line client. - `vcli `_: Vertica client. - `aws-shell `_: An integrated shell for working with the AWS CLI. - `softlayer-python `_: A command-line interface to manage various SoftLayer products and services. - `ipython `_: The IPython REPL - `click-repl `_: Subcommand REPL for click apps. - `haxor-news `_: A Hacker News CLI. - `gitsome `_: A Git/Shell Autocompleter with GitHub Integration. - `http-prompt `_: An interactive command-line HTTP client. - `coconut `_: Functional programming in Python. - `Ergonomica `_: A Bash alternative written in Python. - `Kube-shell `_: Kubernetes shell: An integrated shell for working with the Kubernetes CLI - `mssql-cli `_: A command-line client for Microsoft SQL Server. - `robotframework-debuglibrary `_: A debug library and REPL for RobotFramework. - `ptrepl `_: Run any command as REPL - `clipwdmgr `_: Command Line Password Manager. - `slacker `_: Easy access to the Slack API and admin of workspaces via REPL. - `EdgeDB `_: The next generation object-relational database. - `pywit `_: Python library for Wit.ai. - `objection `_: Runtime Mobile Exploration. - `habu `_: Python Network Hacking Toolkit. - `nawano `_: Nano cryptocurrency wallet - `athenacli `_: A CLI for AWS Athena. - `vulcano `_: A framework for creating command-line applications that also runs in REPL mode. - `kafka-shell `_: A supercharged shell for Apache Kafka. Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure Python. - `freud `_: REST client backed by SQLite for storing servers - `pypager `_: A $PAGER in pure Python (like "less"). - `kubeterminal `_: Kubectl helper tool. Libraries: - `ptterm `_: A terminal emulator widget for prompt_toolkit. - `PyInquirer `_: A Python library that wants to make it easy for existing Inquirer.js users to write immersive command line applications in Python. (Want your own project to be listed here? Please create a GitHub issue.) Philosophy ********** The source code of ``prompt_toolkit`` should be readable, concise and efficient. We prefer short functions focusing each on one task and for which the input and output types are clearly specified. We mostly prefer composition over inheritance, because inheritance can result in too much functionality in the same object. We prefer immutable objects where possible (objects don't change after initialization). Reusability is important. We absolutely refrain from having a changing global state, it should be possible to have multiple independent instances of the same code in the same process. The architecture should be layered: the lower levels operate on primitive operations and data structures giving -- when correctly combined -- all the possible flexibility; while at the higher level, there should be a simpler API, ready-to-use and sufficient for most use cases. Thinking about algorithms and efficiency is important, but avoid premature optimization. Special thanks to ***************** - `Pygments `_: Syntax highlighter. - `wcwidth `_: Determine columns needed for a wide characters. .. |Build Status| image:: https://api.travis-ci.org/prompt-toolkit/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/prompt-toolkit/python-prompt-toolkit# .. |PyPI| image:: https://img.shields.io/pypi/v/prompt_toolkit.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version .. |AppVeyor| image:: https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true :target: https://ci.appveyor.com/project/prompt-toolkit/python-prompt-toolkit/ .. |RTD| image:: https://readthedocs.org/projects/python-prompt-toolkit/badge/ :target: https://python-prompt-toolkit.readthedocs.io/en/master/ .. |License| image:: https://img.shields.io/github/license/prompt-toolkit/python-prompt-toolkit.svg :target: https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/LICENSE .. |Codecov| image:: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/branch/master/graphs/badge.svg?style=flat :target: https://codecov.io/gh/prompt-toolkit/python-prompt-toolkit/ Other libraries and implementations in other languages ****************************************************** - `go-prompt `_: building a powerful interactive prompt in Go, inspired by python-prompt-toolkit. - `urwid `_: Console user interface library for Python. Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python Classifier: Topic :: Software Development Requires-Python: >=2.6, !=3.0.*, !=3.1.*, !=3.2.* prompt_toolkit-2.0.10/prompt_toolkit.egg-info/SOURCES.txt0000644000175100017510000002463413545410361024767 0ustar jonathanjonathan00000000000000AUTHORS.rst CHANGELOG LICENSE MANIFEST.in README.rst setup.cfg setup.py examples/gevent-get-input.py examples/dialogs/button_dialog.py examples/dialogs/input_dialog.py examples/dialogs/messagebox.py examples/dialogs/password_dialog.py examples/dialogs/progress_dialog.py examples/dialogs/radio_dialog.py examples/dialogs/styled_messagebox.py examples/dialogs/yes_no_dialog.py examples/full-screen/buttons.py examples/full-screen/calculator.py examples/full-screen/dummy-app.py examples/full-screen/full-screen-demo.py examples/full-screen/hello-world-asyncio.py examples/full-screen/hello-world.py examples/full-screen/no-layout.py examples/full-screen/pager.py examples/full-screen/split-screen.py examples/full-screen/text-editor.py examples/full-screen/simple-demos/alignment.py examples/full-screen/simple-demos/autocompletion.py examples/full-screen/simple-demos/colorcolumn.py examples/full-screen/simple-demos/cursorcolumn-cursorline.py examples/full-screen/simple-demos/float-transparency.py examples/full-screen/simple-demos/floats.py examples/full-screen/simple-demos/focus.py examples/full-screen/simple-demos/horizontal-align.py examples/full-screen/simple-demos/horizontal-split.py examples/full-screen/simple-demos/line-prefixes.py examples/full-screen/simple-demos/margins.py examples/full-screen/simple-demos/vertical-align.py examples/full-screen/simple-demos/vertical-split.py examples/other/coroutines-and-futures.py examples/print-text/ansi-colors.py examples/print-text/ansi.py examples/print-text/html.py examples/print-text/named-colors.py examples/print-text/print-formatted-text.py examples/print-text/print-frame.py examples/print-text/pygments-tokens.py examples/print-text/true-color-demo.py examples/progress-bar/a-lot-of-parallel-tasks.py examples/progress-bar/colored-title-and-label.py examples/progress-bar/custom-key-bindings.py examples/progress-bar/many-parallel-tasks.py examples/progress-bar/nested-progress-bars.py examples/progress-bar/scrolling-task-name.py examples/progress-bar/simple-progress-bar.py examples/progress-bar/styled-1.py examples/progress-bar/styled-2.py examples/progress-bar/styled-apt-get-install.py examples/progress-bar/styled-rainbow.py examples/progress-bar/styled-tqdm-1.py examples/progress-bar/styled-tqdm-2.py examples/progress-bar/two-tasks.py examples/progress-bar/unknown-length.py examples/prompts/accept-default.py examples/prompts/asyncio-prompt.py examples/prompts/auto-suggestion.py examples/prompts/autocorrection.py examples/prompts/bottom-toolbar.py examples/prompts/clock-input.py examples/prompts/colored-prompt.py examples/prompts/confirmation-prompt.py examples/prompts/custom-key-binding.py examples/prompts/custom-lexer.py examples/prompts/custom-vi-operator-and-text-object.py examples/prompts/finalterm-shell-integration.py examples/prompts/get-input-vi-mode.py examples/prompts/get-input-with-default.py examples/prompts/get-input.py examples/prompts/get-multiline-input.py examples/prompts/get-password-with-toggle-display-shortcut.py examples/prompts/get-password.py examples/prompts/html-input.py examples/prompts/input-validation.py examples/prompts/inputhook.py examples/prompts/mouse-support.py examples/prompts/multiline-prompt.py examples/prompts/no-wrapping.py examples/prompts/operate-and-get-next.py examples/prompts/patch-stdout.py examples/prompts/regular-language.py examples/prompts/rprompt.py examples/prompts/swap-light-and-dark-colors.py examples/prompts/switch-between-vi-emacs.py examples/prompts/system-clipboard-integration.py examples/prompts/system-prompt.py examples/prompts/terminal-title.py examples/prompts/up-arrow-partial-string-matching.py examples/prompts/auto-completion/autocomplete-with-control-space.py examples/prompts/auto-completion/autocompletion-like-readline.py examples/prompts/auto-completion/autocompletion.py examples/prompts/auto-completion/colored-completions-with-formatted-text.py examples/prompts/auto-completion/colored-completions.py examples/prompts/auto-completion/combine-multiple-completers.py examples/prompts/auto-completion/fuzzy-custom-completer.py examples/prompts/auto-completion/fuzzy-word-completer.py examples/prompts/auto-completion/multi-column-autocompletion-with-meta.py examples/prompts/auto-completion/multi-column-autocompletion.py examples/prompts/auto-completion/slow-completions.py examples/prompts/history/persistent-history.py examples/prompts/history/slow-history.py examples/telnet/chat-app.py examples/telnet/dialog.py examples/telnet/hello-world-asyncio.py examples/telnet/hello-world.py examples/telnet/toolbar.py examples/tutorial/sqlite-cli.py prompt_toolkit/__init__.py prompt_toolkit/auto_suggest.py prompt_toolkit/buffer.py prompt_toolkit/cache.py prompt_toolkit/document.py prompt_toolkit/enums.py prompt_toolkit/history.py prompt_toolkit/keys.py prompt_toolkit/log.py prompt_toolkit/mouse_events.py prompt_toolkit/patch_stdout.py prompt_toolkit/renderer.py prompt_toolkit/search.py prompt_toolkit/selection.py prompt_toolkit/token.py prompt_toolkit/utils.py prompt_toolkit/validation.py prompt_toolkit/win32_types.py prompt_toolkit.egg-info/PKG-INFO prompt_toolkit.egg-info/SOURCES.txt prompt_toolkit.egg-info/dependency_links.txt prompt_toolkit.egg-info/requires.txt prompt_toolkit.egg-info/top_level.txt prompt_toolkit/application/__init__.py prompt_toolkit/application/application.py prompt_toolkit/application/current.py prompt_toolkit/application/dummy.py prompt_toolkit/application/run_in_terminal.py prompt_toolkit/clipboard/__init__.py prompt_toolkit/clipboard/base.py prompt_toolkit/clipboard/in_memory.py prompt_toolkit/clipboard/pyperclip.py prompt_toolkit/completion/__init__.py prompt_toolkit/completion/base.py prompt_toolkit/completion/filesystem.py prompt_toolkit/completion/fuzzy_completer.py prompt_toolkit/completion/word_completer.py prompt_toolkit/contrib/__init__.py prompt_toolkit/contrib/completers/__init__.py prompt_toolkit/contrib/completers/system.py prompt_toolkit/contrib/regular_languages/__init__.py prompt_toolkit/contrib/regular_languages/compiler.py prompt_toolkit/contrib/regular_languages/completion.py prompt_toolkit/contrib/regular_languages/lexer.py prompt_toolkit/contrib/regular_languages/regex_parser.py prompt_toolkit/contrib/regular_languages/validation.py prompt_toolkit/contrib/telnet/__init__.py prompt_toolkit/contrib/telnet/log.py prompt_toolkit/contrib/telnet/protocol.py prompt_toolkit/contrib/telnet/server.py prompt_toolkit/eventloop/__init__.py prompt_toolkit/eventloop/async_generator.py prompt_toolkit/eventloop/asyncio_posix.py prompt_toolkit/eventloop/asyncio_win32.py prompt_toolkit/eventloop/base.py prompt_toolkit/eventloop/context.py prompt_toolkit/eventloop/coroutine.py prompt_toolkit/eventloop/defaults.py prompt_toolkit/eventloop/event.py prompt_toolkit/eventloop/future.py prompt_toolkit/eventloop/inputhook.py prompt_toolkit/eventloop/posix.py prompt_toolkit/eventloop/select.py prompt_toolkit/eventloop/utils.py prompt_toolkit/eventloop/win32.py prompt_toolkit/filters/__init__.py prompt_toolkit/filters/app.py prompt_toolkit/filters/base.py prompt_toolkit/filters/cli.py prompt_toolkit/filters/utils.py prompt_toolkit/formatted_text/__init__.py prompt_toolkit/formatted_text/ansi.py prompt_toolkit/formatted_text/base.py prompt_toolkit/formatted_text/html.py prompt_toolkit/formatted_text/pygments.py prompt_toolkit/formatted_text/utils.py prompt_toolkit/input/__init__.py prompt_toolkit/input/ansi_escape_sequences.py prompt_toolkit/input/base.py prompt_toolkit/input/defaults.py prompt_toolkit/input/posix_pipe.py prompt_toolkit/input/posix_utils.py prompt_toolkit/input/typeahead.py prompt_toolkit/input/vt100.py prompt_toolkit/input/vt100_parser.py prompt_toolkit/input/win32.py prompt_toolkit/input/win32_pipe.py prompt_toolkit/key_binding/__init__.py prompt_toolkit/key_binding/defaults.py prompt_toolkit/key_binding/digraphs.py prompt_toolkit/key_binding/emacs_state.py prompt_toolkit/key_binding/key_bindings.py prompt_toolkit/key_binding/key_processor.py prompt_toolkit/key_binding/vi_state.py prompt_toolkit/key_binding/bindings/__init__.py prompt_toolkit/key_binding/bindings/auto_suggest.py prompt_toolkit/key_binding/bindings/basic.py prompt_toolkit/key_binding/bindings/completion.py prompt_toolkit/key_binding/bindings/cpr.py prompt_toolkit/key_binding/bindings/emacs.py prompt_toolkit/key_binding/bindings/focus.py prompt_toolkit/key_binding/bindings/mouse.py prompt_toolkit/key_binding/bindings/named_commands.py prompt_toolkit/key_binding/bindings/open_in_editor.py prompt_toolkit/key_binding/bindings/page_navigation.py prompt_toolkit/key_binding/bindings/scroll.py prompt_toolkit/key_binding/bindings/search.py prompt_toolkit/key_binding/bindings/vi.py prompt_toolkit/layout/__init__.py prompt_toolkit/layout/containers.py prompt_toolkit/layout/controls.py prompt_toolkit/layout/dimension.py prompt_toolkit/layout/dummy.py prompt_toolkit/layout/layout.py prompt_toolkit/layout/margins.py prompt_toolkit/layout/menus.py prompt_toolkit/layout/mouse_handlers.py prompt_toolkit/layout/processors.py prompt_toolkit/layout/screen.py prompt_toolkit/layout/utils.py prompt_toolkit/lexers/__init__.py prompt_toolkit/lexers/base.py prompt_toolkit/lexers/pygments.py prompt_toolkit/output/__init__.py prompt_toolkit/output/base.py prompt_toolkit/output/color_depth.py prompt_toolkit/output/conemu.py prompt_toolkit/output/defaults.py prompt_toolkit/output/vt100.py prompt_toolkit/output/win32.py prompt_toolkit/output/windows10.py prompt_toolkit/shortcuts/__init__.py prompt_toolkit/shortcuts/dialogs.py prompt_toolkit/shortcuts/prompt.py prompt_toolkit/shortcuts/utils.py prompt_toolkit/shortcuts/progress_bar/__init__.py prompt_toolkit/shortcuts/progress_bar/base.py prompt_toolkit/shortcuts/progress_bar/formatters.py prompt_toolkit/styles/__init__.py prompt_toolkit/styles/base.py prompt_toolkit/styles/defaults.py prompt_toolkit/styles/named_colors.py prompt_toolkit/styles/pygments.py prompt_toolkit/styles/style.py prompt_toolkit/styles/style_transformation.py prompt_toolkit/widgets/__init__.py prompt_toolkit/widgets/base.py prompt_toolkit/widgets/dialogs.py prompt_toolkit/widgets/menus.py prompt_toolkit/widgets/toolbars.py tests/test_async_generator.py tests/test_buffer.py tests/test_cli.py tests/test_completion.py tests/test_document.py tests/test_filter.py tests/test_formatted_text.py tests/test_inputstream.py tests/test_key_binding.py tests/test_layout.py tests/test_print_formatted_text.py tests/test_regular_languages.py tests/test_shortcuts.py tests/test_style.py tests/test_style_transformation.py tests/test_utils.py tests/test_vt100_output.py tests/test_yank_nth_arg.pyprompt_toolkit-2.0.10/prompt_toolkit.egg-info/dependency_links.txt0000644000175100017510000000000113545410360027137 0ustar jonathanjonathan00000000000000 prompt_toolkit-2.0.10/prompt_toolkit.egg-info/requires.txt0000644000175100017510000000002313545410360025464 0ustar jonathanjonathan00000000000000six>=1.9.0 wcwidth prompt_toolkit-2.0.10/prompt_toolkit.egg-info/top_level.txt0000644000175100017510000000001713545410360025621 0ustar jonathanjonathan00000000000000prompt_toolkit prompt_toolkit-2.0.10/setup.cfg0000644000175100017510000000051213545410361020131 0ustar jonathanjonathan00000000000000[flake8] exclude = __init__.py max_line_length = 150 ignore = E114, E116, E117, E121, E122, E123, E126, E127, E128, E131, E171, E211, E221, E227, E241, E251, E301, E501, E701, E702, E704, E731, E741, F403, F405, F811, W503, W504 [pytest:tool] testpaths = tests [egg_info] tag_build = tag_date = 0 prompt_toolkit-2.0.10/setup.py0000755000175100017510000000334613545407204020037 0ustar jonathanjonathan00000000000000#!/usr/bin/env python import os import re from setuptools import find_packages, setup with open(os.path.join(os.path.dirname(__file__), 'README.rst')) as f: long_description = f.read() def get_version(package): """ Return package version as listed in `__version__` in `__init__.py`. """ path = os.path.join(os.path.dirname(__file__), package, '__init__.py') with open(path, 'rb') as f: init_py = f.read().decode('utf-8') return re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1) setup( name='prompt_toolkit', author='Jonathan Slenders', version=get_version('prompt_toolkit'), license='BSD-3-Clause', url='https://github.com/jonathanslenders/python-prompt-toolkit', description='Library for building powerful interactive command lines in Python', long_description=long_description, packages=find_packages('.'), install_requires=[ 'six>=1.9.0', 'wcwidth', ], python_requires='>=2.6, !=3.0.*, !=3.1.*, !=3.2.*', classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python', 'Topic :: Software Development', ], ) prompt_toolkit-2.0.10/tests/0000755000175100017510000000000013545410361017454 5ustar jonathanjonathan00000000000000prompt_toolkit-2.0.10/tests/test_async_generator.py0000644000175100017510000000400213545407204024246 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.eventloop import ( AsyncGeneratorItem, From, Future, consume_async_generator, ensure_future, generator_to_async_generator, get_event_loop, ) def _async_generator(): " Simple asynchronous generator. " # await. result = yield From(Future.succeed(1)) # yield yield AsyncGeneratorItem(result + 1) # await. result = yield From(Future.succeed(10)) # yield yield AsyncGeneratorItem(result + 1) def test_async_generator(): " Test asynchronous generator. " items = [] f = ensure_future(consume_async_generator( _async_generator(), lambda: False, items.append)) # Run the event loop until all items are collected. get_event_loop().run_until_complete(f) assert items == [2, 11] # Check that `consume_async_generator` didn't fail. assert f.result() is None def _empty_async_generator(): " Async generator that returns right away. " if False: yield def test_empty_async_generator(): " Test asynchronous generator. " items = [] f = ensure_future(consume_async_generator( _empty_async_generator(), lambda: False, items.append)) # Run the event loop until all items are collected. get_event_loop().run_until_complete(f) assert items == [] # Check that `consume_async_generator` didn't fail. assert f.result() is None def _sync_generator(): yield 1 yield 10 def test_generator_to_async_generator(): """ Test conversion of sync to asycn generator. This should run the synchronous parts in a background thread. """ async_gen = generator_to_async_generator(_sync_generator) items = [] f = ensure_future(consume_async_generator( async_gen, lambda: False, items.append)) # Run the event loop until all items are collected. get_event_loop().run_until_complete(f) assert items == [1, 10] # Check that `consume_async_generator` didn't fail. assert f.result() is None prompt_toolkit-2.0.10/tests/test_buffer.py0000644000175100017510000000514713545407204022347 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.buffer import Buffer @pytest.fixture def _buffer(): buff = Buffer() return buff def test_initial(_buffer): assert _buffer.text == '' assert _buffer.cursor_position == 0 def test_insert_text(_buffer): _buffer.insert_text('some_text') assert _buffer.text == 'some_text' assert _buffer.cursor_position == len('some_text') def test_cursor_movement(_buffer): _buffer.insert_text('some_text') _buffer.cursor_left() _buffer.cursor_left() _buffer.cursor_left() _buffer.cursor_right() _buffer.insert_text('A') assert _buffer.text == 'some_teAxt' assert _buffer.cursor_position == len('some_teA') def test_backspace(_buffer): _buffer.insert_text('some_text') _buffer.cursor_left() _buffer.cursor_left() _buffer.delete_before_cursor() assert _buffer.text == 'some_txt' assert _buffer.cursor_position == len('some_t') def test_cursor_up(_buffer): # Cursor up to a line thats longer. _buffer.insert_text('long line1\nline2') _buffer.cursor_up() assert _buffer.document.cursor_position == 5 # Going up when already at the top. _buffer.cursor_up() assert _buffer.document.cursor_position == 5 # Going up to a line that's shorter. _buffer.reset() _buffer.insert_text('line1\nlong line2') _buffer.cursor_up() assert _buffer.document.cursor_position == 5 def test_cursor_down(_buffer): _buffer.insert_text('line1\nline2') _buffer.cursor_position = 3 # Normally going down _buffer.cursor_down() assert _buffer.document.cursor_position == len('line1\nlin') # Going down to a line that's shorter. _buffer.reset() _buffer.insert_text('long line1\na\nb') _buffer.cursor_position = 3 _buffer.cursor_down() assert _buffer.document.cursor_position == len('long line1\na') def test_join_next_line(_buffer): _buffer.insert_text('line1\nline2\nline3') _buffer.cursor_up() _buffer.join_next_line() assert _buffer.text == 'line1\nline2 line3' # Test when there is no '\n' in the text _buffer.reset() _buffer.insert_text('line1') _buffer.cursor_position = 0 _buffer.join_next_line() assert _buffer.text == 'line1' def test_newline(_buffer): _buffer.insert_text('hello world') _buffer.newline() assert _buffer.text == 'hello world\n' def test_swap_characters_before_cursor(_buffer): _buffer.insert_text('hello world') _buffer.cursor_left() _buffer.cursor_left() _buffer.swap_characters_before_cursor() assert _buffer.text == 'hello wrold' prompt_toolkit-2.0.10/tests/test_cli.py0000644000175100017510000006713313545407204021650 0ustar jonathanjonathan00000000000000# encoding: utf-8 """ These are almost end-to-end tests. They create a Prompt, feed it with some input and check the result. """ from __future__ import unicode_literals from functools import partial import pytest from prompt_toolkit.clipboard import ClipboardData, InMemoryClipboard from prompt_toolkit.enums import EditingMode from prompt_toolkit.filters import ViInsertMode from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.input.defaults import create_pipe_input from prompt_toolkit.input.vt100_parser import ANSI_SEQUENCES from prompt_toolkit.key_binding.bindings.named_commands import prefix_meta from prompt_toolkit.key_binding.key_bindings import KeyBindings from prompt_toolkit.output import DummyOutput from prompt_toolkit.shortcuts import PromptSession def _history(): h = InMemoryHistory() h.append_string('line1 first input') h.append_string('line2 second input') h.append_string('line3 third input') return h def _feed_cli_with_input( text, editing_mode=EditingMode.EMACS, clipboard=None, history=None, multiline=False, check_line_ending=True, key_bindings=None): """ Create a Prompt, feed it with the given user input and return the CLI object. This returns a (result, Application) tuple. """ # If the given text doesn't end with a newline, the interface won't finish. if check_line_ending: assert text.endswith('\r') inp = create_pipe_input() try: inp.send_text(text) session = PromptSession( input=inp, output=DummyOutput(), editing_mode=editing_mode, history=history, multiline=multiline, clipboard=clipboard, key_bindings=key_bindings) result = session.prompt() return session.default_buffer.document, session.app finally: inp.close() def test_simple_text_input(): # Simple text input, followed by enter. result, cli = _feed_cli_with_input('hello\r') assert result.text == 'hello' assert cli.current_buffer.text == 'hello' def test_emacs_cursor_movements(): """ Test cursor movements with Emacs key bindings. """ # ControlA (beginning-of-line) result, cli = _feed_cli_with_input('hello\x01X\r') assert result.text == 'Xhello' # ControlE (end-of-line) result, cli = _feed_cli_with_input('hello\x01X\x05Y\r') assert result.text == 'XhelloY' # ControlH or \b result, cli = _feed_cli_with_input('hello\x08X\r') assert result.text == 'hellX' # Delete. (Left, left, delete) result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x1b[3~\r') assert result.text == 'helo' # Left. result, cli = _feed_cli_with_input('hello\x1b[DX\r') assert result.text == 'hellXo' # ControlA, right result, cli = _feed_cli_with_input('hello\x01\x1b[CX\r') assert result.text == 'hXello' # ControlB (backward-char) result, cli = _feed_cli_with_input('hello\x02X\r') assert result.text == 'hellXo' # ControlF (forward-char) result, cli = _feed_cli_with_input('hello\x01\x06X\r') assert result.text == 'hXello' # ControlD: delete after cursor. result, cli = _feed_cli_with_input('hello\x01\x04\r') assert result.text == 'ello' # ControlD at the end of the input ssshould not do anything. result, cli = _feed_cli_with_input('hello\x04\r') assert result.text == 'hello' # Left, Left, ControlK (kill-line) result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x0b\r') assert result.text == 'hel' # Left, Left Esc- ControlK (kill-line, but negative) result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x1b-\x0b\r') assert result.text == 'lo' # ControlL: should not influence the result. result, cli = _feed_cli_with_input('hello\x0c\r') assert result.text == 'hello' # ControlRight (forward-word) result, cli = _feed_cli_with_input('hello world\x01X\x1b[1;5CY\r') assert result.text == 'XhelloY world' # ContrlolLeft (backward-word) result, cli = _feed_cli_with_input('hello world\x1b[1;5DY\r') assert result.text == 'hello Yworld' # -f with argument. (forward-word) result, cli = _feed_cli_with_input('hello world abc def\x01\x1b3\x1bfX\r') assert result.text == 'hello world abcX def' # -f with negative argument. (forward-word) result, cli = _feed_cli_with_input('hello world abc def\x1b-\x1b3\x1bfX\r') assert result.text == 'hello Xworld abc def' # -b with argument. (backward-word) result, cli = _feed_cli_with_input('hello world abc def\x1b3\x1bbX\r') assert result.text == 'hello Xworld abc def' # -b with negative argument. (backward-word) result, cli = _feed_cli_with_input('hello world abc def\x01\x1b-\x1b3\x1bbX\r') assert result.text == 'hello world abc Xdef' # ControlW (kill-word / unix-word-rubout) result, cli = _feed_cli_with_input('hello world\x17\r') assert result.text == 'hello ' assert cli.clipboard.get_data().text == 'world' result, cli = _feed_cli_with_input('test hello world\x1b2\x17\r') assert result.text == 'test ' # Escape Backspace (unix-word-rubout) result, cli = _feed_cli_with_input('hello world\x1b\x7f\r') assert result.text == 'hello ' assert cli.clipboard.get_data().text == 'world' result, cli = _feed_cli_with_input('hello world\x1b\x08\r') assert result.text == 'hello ' assert cli.clipboard.get_data().text == 'world' # Backspace (backward-delete-char) result, cli = _feed_cli_with_input('hello world\x7f\r') assert result.text == 'hello worl' assert result.cursor_position == len('hello worl') result, cli = _feed_cli_with_input('hello world\x08\r') assert result.text == 'hello worl' assert result.cursor_position == len('hello worl') # Delete (delete-char) result, cli = _feed_cli_with_input('hello world\x01\x1b[3~\r') assert result.text == 'ello world' assert result.cursor_position == 0 # Escape-\\ (delete-horizontal-space) result, cli = _feed_cli_with_input('hello world\x1b8\x02\x1b\\\r') assert result.text == 'helloworld' assert result.cursor_position == len('hello') def test_emacs_kill_multiple_words_and_paste(): # Using control-w twice should place both words on the clipboard. result, cli = _feed_cli_with_input( 'hello world test' '\x17\x17' # Twice c-w. '--\x19\x19\r' # Twice c-y. ) assert result.text == 'hello --world testworld test' assert cli.clipboard.get_data().text == 'world test' # Using alt-d twice should place both words on the clipboard. result, cli = _feed_cli_with_input( 'hello world test' '\x1bb\x1bb' # Twice left. '\x1bd\x1bd' # Twice kill-word. 'abc' '\x19' # Paste. '\r' ) assert result.text == 'hello abcworld test' assert cli.clipboard.get_data().text == 'world test' def test_interrupts(): # ControlC: raise KeyboardInterrupt. with pytest.raises(KeyboardInterrupt): result, cli = _feed_cli_with_input('hello\x03\r') with pytest.raises(KeyboardInterrupt): result, cli = _feed_cli_with_input('hello\x03\r') # ControlD without any input: raises EOFError. with pytest.raises(EOFError): result, cli = _feed_cli_with_input('\x04\r') def test_emacs_yank(): # ControlY (yank) c = InMemoryClipboard(ClipboardData('XYZ')) result, cli = _feed_cli_with_input('hello\x02\x19\r', clipboard=c) assert result.text == 'hellXYZo' assert result.cursor_position == len('hellXYZ') def test_quoted_insert(): # ControlQ - ControlB (quoted-insert) result, cli = _feed_cli_with_input('hello\x11\x02\r') assert result.text == 'hello\x02' def test_transformations(): # Meta-c (capitalize-word) result, cli = _feed_cli_with_input('hello world\01\x1bc\r') assert result.text == 'Hello world' assert result.cursor_position == len('Hello') # Meta-u (uppercase-word) result, cli = _feed_cli_with_input('hello world\01\x1bu\r') assert result.text == 'HELLO world' assert result.cursor_position == len('Hello') # Meta-u (downcase-word) result, cli = _feed_cli_with_input('HELLO WORLD\01\x1bl\r') assert result.text == 'hello WORLD' assert result.cursor_position == len('Hello') # ControlT (transpose-chars) result, cli = _feed_cli_with_input('hello\x14\r') assert result.text == 'helol' assert result.cursor_position == len('hello') # Left, Left, Control-T (transpose-chars) result, cli = _feed_cli_with_input('abcde\x1b[D\x1b[D\x14\r') assert result.text == 'abdce' assert result.cursor_position == len('abcd') def test_emacs_other_bindings(): # Transpose characters. result, cli = _feed_cli_with_input('abcde\x14X\r') # Ctrl-T assert result.text == 'abcedX' # Left, Left, Transpose. (This is slightly different.) result, cli = _feed_cli_with_input('abcde\x1b[D\x1b[D\x14X\r') assert result.text == 'abdcXe' # Clear before cursor. result, cli = _feed_cli_with_input('hello\x1b[D\x1b[D\x15X\r') assert result.text == 'Xlo' # unix-word-rubout: delete word before the cursor. # (ControlW). result, cli = _feed_cli_with_input('hello world test\x17X\r') assert result.text == 'hello world X' result, cli = _feed_cli_with_input('hello world /some/very/long/path\x17X\r') assert result.text == 'hello world X' # (with argument.) result, cli = _feed_cli_with_input('hello world test\x1b2\x17X\r') assert result.text == 'hello X' result, cli = _feed_cli_with_input('hello world /some/very/long/path\x1b2\x17X\r') assert result.text == 'hello X' # backward-kill-word: delete word before the cursor. # (Esc-ControlH). result, cli = _feed_cli_with_input('hello world /some/very/long/path\x1b\x08X\r') assert result.text == 'hello world /some/very/long/X' # (with arguments.) result, cli = _feed_cli_with_input('hello world /some/very/long/path\x1b3\x1b\x08X\r') assert result.text == 'hello world /some/very/X' def test_controlx_controlx(): # At the end: go to the start of the line. result, cli = _feed_cli_with_input('hello world\x18\x18X\r') assert result.text == 'Xhello world' assert result.cursor_position == 1 # At the start: go to the end of the line. result, cli = _feed_cli_with_input('hello world\x01\x18\x18X\r') assert result.text == 'hello worldX' # Left, Left Control-X Control-X: go to the end of the line. result, cli = _feed_cli_with_input('hello world\x1b[D\x1b[D\x18\x18X\r') assert result.text == 'hello worldX' def test_emacs_history_bindings(): # Adding a new item to the history. history = _history() result, cli = _feed_cli_with_input('new input\r', history=history) assert result.text == 'new input' history.get_strings()[-1] == 'new input' # Go up in history, and accept the last item. result, cli = _feed_cli_with_input('hello\x1b[A\r', history=history) assert result.text == 'new input' # Esc< (beginning-of-history) result, cli = _feed_cli_with_input('hello\x1b<\r', history=history) assert result.text == 'line1 first input' # Esc> (end-of-history) result, cli = _feed_cli_with_input('another item\x1b[A\x1b[a\x1b>\r', history=history) assert result.text == 'another item' # ControlUp (previous-history) result, cli = _feed_cli_with_input('\x1b[1;5A\r', history=history) assert result.text == 'another item' # Esc< ControlDown (beginning-of-history, next-history) result, cli = _feed_cli_with_input('\x1b<\x1b[1;5B\r', history=history) assert result.text == 'line2 second input' def test_emacs_reverse_search(): history = _history() # ControlR (reverse-search-history) result, cli = _feed_cli_with_input('\x12input\r\r', history=history) assert result.text == 'line3 third input' # Hitting ControlR twice. result, cli = _feed_cli_with_input('\x12input\x12\r\r', history=history) assert result.text == 'line2 second input' def test_emacs_arguments(): """ Test various combinations of arguments in Emacs mode. """ # esc 4 result, cli = _feed_cli_with_input('\x1b4x\r') assert result.text == 'xxxx' # esc 4 4 result, cli = _feed_cli_with_input('\x1b44x\r') assert result.text == 'x' * 44 # esc 4 esc 4 result, cli = _feed_cli_with_input('\x1b4\x1b4x\r') assert result.text == 'x' * 44 # esc - right (-1 position to the right, equals 1 to the left.) result, cli = _feed_cli_with_input('aaaa\x1b-\x1b[Cbbbb\r') assert result.text == 'aaabbbba' # esc - 3 right result, cli = _feed_cli_with_input('aaaa\x1b-3\x1b[Cbbbb\r') assert result.text == 'abbbbaaa' # esc - - - 3 right result, cli = _feed_cli_with_input('aaaa\x1b---3\x1b[Cbbbb\r') assert result.text == 'abbbbaaa' def test_emacs_arguments_for_all_commands(): """ Test all Emacs commands with Meta-[0-9] arguments (both positive and negative). No one should crash. """ for key in ANSI_SEQUENCES: # Ignore BracketedPaste. This would hang forever, because it waits for # the end sequence. if key != '\x1b[200~': try: # Note: we add an 'X' after the key, because Ctrl-Q (quoted-insert) # expects something to follow. We add an additional \r, because # Ctrl-R and Ctrl-S (reverse-search) expect that. result, cli = _feed_cli_with_input( 'hello\x1b4' + key + 'X\r\r') result, cli = _feed_cli_with_input( 'hello\x1b-' + key + 'X\r\r') except KeyboardInterrupt: # This exception should only be raised for Ctrl-C assert key == '\x03' def test_emacs_kill_ring(): operations = ( # abc ControlA ControlK 'abc\x01\x0b' # def ControlA ControlK 'def\x01\x0b' # ghi ControlA ControlK 'ghi\x01\x0b' # ControlY (yank) '\x19' ) result, cli = _feed_cli_with_input(operations + '\r') assert result.text == 'ghi' result, cli = _feed_cli_with_input(operations + '\x1by\r') assert result.text == 'def' result, cli = _feed_cli_with_input(operations + '\x1by\x1by\r') assert result.text == 'abc' result, cli = _feed_cli_with_input(operations + '\x1by\x1by\x1by\r') assert result.text == 'ghi' def test_emacs_selection(): # Copy/paste empty selection should not do anything. operations = ( 'hello' # Twice left. '\x1b[D\x1b[D' # Control-Space '\x00' # ControlW (cut) '\x17' # ControlY twice. (paste twice) '\x19\x19\r' ) result, cli = _feed_cli_with_input(operations) assert result.text == 'hello' # Copy/paste one character. operations = ( 'hello' # Twice left. '\x1b[D\x1b[D' # Control-Space '\x00' # Right. '\x1b[C' # ControlW (cut) '\x17' # ControlA (Home). '\x01' # ControlY (paste) '\x19\r' ) result, cli = _feed_cli_with_input(operations) assert result.text == 'lhelo' def test_emacs_insert_comment(): # Test insert-comment (M-#) binding. result, cli = _feed_cli_with_input('hello\x1b#', check_line_ending=False) assert result.text == '#hello' result, cli = _feed_cli_with_input( 'hello\rworld\x1b#', check_line_ending=False, multiline=True) assert result.text == '#hello\n#world' def test_emacs_record_macro(): operations = ( ' ' '\x18(' # Start recording macro. C-X( 'hello' '\x18)' # Stop recording macro. ' ' '\x18e' # Execute macro. '\x18e' # Execute macro. '\r' ) result, cli = _feed_cli_with_input(operations) assert result.text == ' hello hellohello' def test_emacs_nested_macro(): " Test calling the macro within a macro. " # Calling a macro within a macro should take the previous recording (if one # exists), not the one that is in progress. operations = ( '\x18(' # Start recording macro. C-X( 'hello' '\x18e' # Execute macro. '\x18)' # Stop recording macro. '\x18e' # Execute macro. '\r' ) result, cli = _feed_cli_with_input(operations) assert result.text == 'hellohello' operations = ( '\x18(' # Start recording macro. C-X( 'hello' '\x18)' # Stop recording macro. '\x18(' # Start recording macro. C-X( '\x18e' # Execute macro. 'world' '\x18)' # Stop recording macro. '\x01\x0b' # Delete all (c-a c-k). '\x18e' # Execute macro. '\r' ) result, cli = _feed_cli_with_input(operations) assert result.text == 'helloworld' def test_prefix_meta(): # Test the prefix-meta command. b = KeyBindings() b.add('j', 'j', filter=ViInsertMode())(prefix_meta) result, cli = _feed_cli_with_input( 'hellojjIX\r', key_bindings=b, editing_mode=EditingMode.VI) assert result.text == 'Xhello' def test_bracketed_paste(): result, cli = _feed_cli_with_input('\x1b[200~hello world\x1b[201~\r') assert result.text == 'hello world' result, cli = _feed_cli_with_input('\x1b[200~hello\rworld\x1b[201~\x1b\r') assert result.text == 'hello\nworld' # With \r\n endings. result, cli = _feed_cli_with_input('\x1b[200~hello\r\nworld\x1b[201~\x1b\r') assert result.text == 'hello\nworld' # With \n endings. result, cli = _feed_cli_with_input('\x1b[200~hello\nworld\x1b[201~\x1b\r') assert result.text == 'hello\nworld' def test_vi_cursor_movements(): """ Test cursor movements with Vi key bindings. """ feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) result, cli = feed('\x1b\r') assert result.text == '' assert cli.editing_mode == EditingMode.VI # Esc h a X result, cli = feed('hello\x1bhaX\r') assert result.text == 'hellXo' # Esc I X result, cli = feed('hello\x1bIX\r') assert result.text == 'Xhello' # Esc I X result, cli = feed('hello\x1bIX\r') assert result.text == 'Xhello' # Esc 2hiX result, cli = feed('hello\x1b2hiX\r') assert result.text == 'heXllo' # Esc 2h2liX result, cli = feed('hello\x1b2h2liX\r') assert result.text == 'hellXo' # Esc \b\b result, cli = feed('hello\b\b\r') assert result.text == 'hel' # Esc \b\b result, cli = feed('hello\b\b\r') assert result.text == 'hel' # Esc 2h D result, cli = feed('hello\x1b2hD\r') assert result.text == 'he' # Esc 2h rX \r result, cli = feed('hello\x1b2hrX\r') assert result.text == 'heXlo' def test_vi_operators(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # Esc g~0 result, cli = feed('hello\x1bg~0\r') assert result.text == 'HELLo' # Esc gU0 result, cli = feed('hello\x1bgU0\r') assert result.text == 'HELLo' # Esc d0 result, cli = feed('hello\x1bd0\r') assert result.text == 'o' def test_vi_text_objects(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # Esc gUgg result, cli = feed('hello\x1bgUgg\r') assert result.text == 'HELLO' # Esc gUU result, cli = feed('hello\x1bgUU\r') assert result.text == 'HELLO' # Esc di( result, cli = feed('before(inside)after\x1b8hdi(\r') assert result.text == 'before()after' # Esc di[ result, cli = feed('before[inside]after\x1b8hdi[\r') assert result.text == 'before[]after' # Esc da( result, cli = feed('before(inside)after\x1b8hda(\r') assert result.text == 'beforeafter' def test_vi_digraphs(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # C-K o/ result, cli = feed('hello\x0bo/\r') assert result.text == 'helloø' # C-K /o (reversed input.) result, cli = feed('hello\x0b/o\r') assert result.text == 'helloø' # C-K e: result, cli = feed('hello\x0be:\r') assert result.text == 'helloë' # C-K xxy (Unknown digraph.) result, cli = feed('hello\x0bxxy\r') assert result.text == 'helloy' def test_vi_block_editing(): " Test Vi Control-V style block insertion. " feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) operations = ( # Six lines of text. '-line1\r-line2\r-line3\r-line4\r-line5\r-line6' # Go to the second character of the second line. '\x1bkkkkkkkj0l' # Enter Visual block mode. '\x16' # Go down two more lines. 'jj' # Go 3 characters to the right. 'lll' # Go to insert mode. 'insert' # (Will be replaced.) # Insert stars. '***' # Escape again. '\x1b\r') # Control-I result, cli = feed(operations.replace('insert', 'I')) assert (result.text == '-line1\n-***line2\n-***line3\n-***line4\n-line5\n-line6') # Control-A result, cli = feed(operations.replace('insert', 'A')) assert (result.text == '-line1\n-line***2\n-line***3\n-line***4\n-line5\n-line6') def test_vi_block_editing_empty_lines(): " Test block editing on empty lines. " feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) operations = ( # Six empty lines. '\r\r\r\r\r' # Go to beginning of the document. '\x1bgg' # Enter Visual block mode. '\x16' # Go down two more lines. 'jj' # Go 3 characters to the right. 'lll' # Go to insert mode. 'insert' # (Will be replaced.) # Insert stars. '***' # Escape again. '\x1b\r') # Control-I result, cli = feed(operations.replace('insert', 'I')) assert result.text == '***\n***\n***\n\n\n' # Control-A result, cli = feed(operations.replace('insert', 'A')) assert result.text == '***\n***\n***\n\n\n' def test_vi_visual_line_copy(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) operations = ( # Three lines of text. '-line1\r-line2\r-line3\r-line4\r-line5\r-line6' # Go to the second character of the second line. '\x1bkkkkkkkj0l' # Enter Visual linemode. 'V' # Go down one line. 'j' # Go 3 characters to the right (should not do much). 'lll' # Copy this block. 'y' # Go down one line. 'j' # Insert block twice. '2p' # Escape again. '\x1b\r') result, cli = feed(operations) assert (result.text == '-line1\n-line2\n-line3\n-line4\n-line2\n-line3\n-line2\n-line3\n-line5\n-line6') def test_vi_visual_empty_line(): """ Test edge case with an empty line in Visual-line mode. """ feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) # 1. Delete first two lines. operations = ( # Three lines of text. The middle one is empty. 'hello\r\rworld' # Go to the start. '\x1bgg' # Visual line and move down. 'Vj' # Delete. 'd\r') result, cli = feed(operations) assert result.text == 'world' # 1. Delete middle line. operations = ( # Three lines of text. The middle one is empty. 'hello\r\rworld' # Go to middle line. '\x1bggj' # Delete line 'Vd\r') result, cli = feed(operations) assert result.text == 'hello\nworld' def test_vi_character_delete_after_cursor(): " Test 'x' keypress. " feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) # Delete one character. result, cli = feed('abcd\x1bHx\r') assert result.text == 'bcd' # Delete multiple character.s result, cli = feed('abcd\x1bH3x\r') assert result.text == 'd' # Delete on empty line. result, cli = feed('\x1bo\x1bo\x1bggx\r') assert result.text == '\n\n' # Delete multiple on empty line. result, cli = feed('\x1bo\x1bo\x1bgg10x\r') assert result.text == '\n\n' # Delete multiple on empty line. result, cli = feed('hello\x1bo\x1bo\x1bgg3x\r') assert result.text == 'lo\n\n' def test_vi_character_delete_before_cursor(): " Test 'X' keypress. " feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI, multiline=True) # Delete one character. result, cli = feed('abcd\x1bX\r') assert result.text == 'abd' # Delete multiple character. result, cli = feed('hello world\x1b3X\r') assert result.text == 'hello wd' # Delete multiple character on multiple lines. result, cli = feed('hello\x1boworld\x1bgg$3X\r') assert result.text == 'ho\nworld' result, cli = feed('hello\x1boworld\x1b100X\r') assert result.text == 'hello\nd' # Delete on empty line. result, cli = feed('\x1bo\x1bo\x1b10X\r') assert result.text == '\n\n' def test_vi_character_paste(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # Test 'p' character paste. result, cli = feed('abcde\x1bhhxp\r') assert result.text == 'abdce' assert result.cursor_position == 3 # Test 'P' character paste. result, cli = feed('abcde\x1bhhxP\r') assert result.text == 'abcde' assert result.cursor_position == 2 def test_vi_temp_navigation_mode(): """ Test c-o binding: go for one action into navigation mode. """ feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) result, cli = feed( 'abcde' '\x0f' # c-o '3h' # 3 times to the left. 'x\r') assert result.text == 'axbcde' assert result.cursor_position == 2 result, cli = feed( 'abcde' '\x0f' # c-o 'b' # One word backwards. 'x\r') assert result.text == 'xabcde' assert result.cursor_position == 1 # In replace mode result, cli = feed( 'abcdef' '\x1b' # Navigation mode. '0l' # Start of line, one character to the right. 'R' # Replace mode '78' '\x0f' # c-o 'l' # One character forwards. '9\r') assert result.text == 'a78d9f' assert result.cursor_position == 5 def test_vi_macros(): feed = partial(_feed_cli_with_input, editing_mode=EditingMode.VI) # Record and execute macro. result, cli = feed('\x1bqcahello\x1bq@c\r') assert result.text == 'hellohello' assert result.cursor_position == 9 # Running unknown macro. result, cli = feed('\x1b@d\r') assert result.text == '' assert result.cursor_position == 0 # When a macro is called within a macro. # It shouldn't result in eternal recursion. result, cli = feed('\x1bqxahello\x1b@xq@x\r') assert result.text == 'hellohello' assert result.cursor_position == 9 # Nested macros. result, cli = feed( # Define macro 'x'. '\x1bqxahello\x1bq' # Define macro 'y' which calls 'x'. 'qya\x1b@xaworld\x1bq' # Delete line. '2dd' # Execute 'y' '@y\r') assert result.text == 'helloworld' def test_accept_default(): """ Test `prompt(accept_default=True)`. """ inp = create_pipe_input() session = PromptSession(input=inp, output=DummyOutput()) result = session.prompt(default='hello', accept_default=True) assert result == 'hello' # Test calling prompt() for a second time. (We had an issue where the # prompt reset between calls happened at the wrong time, breaking this.) result = session.prompt(default='world', accept_default=True) assert result == 'world' inp.close() prompt_toolkit-2.0.10/tests/test_completion.py0000644000175100017510000003106613545407204023246 0ustar jonathanjonathan00000000000000from __future__ import absolute_import, print_function, unicode_literals import os import re import shutil import tempfile from contextlib import contextmanager from six import text_type from prompt_toolkit.completion import ( CompleteEvent, FuzzyWordCompleter, PathCompleter, WordCompleter, ) from prompt_toolkit.document import Document @contextmanager def chdir(directory): """Context manager for current working directory temporary change.""" orig_dir = os.getcwd() os.chdir(directory) try: yield finally: os.chdir(orig_dir) def write_test_files(test_dir, names=None): """Write test files in test_dir using the names list.""" names = names or range(10) for i in names: with open(os.path.join(test_dir, str(i)), 'wb') as out: out.write(''.encode('UTF-8')) def test_pathcompleter_completes_in_current_directory(): completer = PathCompleter() doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0 def test_pathcompleter_completes_files_in_current_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted([str(i) for i in range(10)]) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter() # this should complete on the cwd doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert expected == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_files_in_absolute_directory(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) expected = sorted([str(i) for i in range(10)]) test_dir = os.path.abspath(test_dir) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep completer = PathCompleter() # force unicode doc_text = text_type(test_dir) doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted([c.text for c in completions]) assert expected == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_directories_with_only_directories(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # create a sub directory there os.mkdir(os.path.join(test_dir, 'subdir')) if not test_dir.endswith(os.path.sep): test_dir += os.path.sep with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ['subdir'] == result # check that there is no completion when passing a file with chdir(test_dir): completer = PathCompleter(only_directories=True) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir) def test_pathcompleter_respects_completions_under_min_input_len(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # min len:1 and no text with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [''] == result # min len:0 and text of len 2 with chdir(test_dir): completer = PathCompleter(min_input_len=0) doc_text = '1' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert [''] == result # create 10 files with a 2 char long name for i in range(10): with open(os.path.join(test_dir, str(i) * 2), 'wb') as out: out.write(b'') # min len:1 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=1) doc_text = '2' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = sorted(c.text for c in completions) assert ['', '2'] == result # min len:2 and text of len 1 with chdir(test_dir): completer = PathCompleter(min_input_len=2) doc_text = '2' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions # cleanup shutil.rmtree(test_dir) def test_pathcompleter_does_not_expanduser_by_default(): completer = PathCompleter() doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert [] == completions def test_pathcompleter_can_expanduser(): completer = PathCompleter(expanduser=True) doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) assert len(completions) > 0 def test_pathcompleter_can_apply_file_filter(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a .csv file with open(os.path.join(test_dir, 'my.csv'), 'wb') as out: out.write(b'') file_filter = lambda f: f and f.endswith('.csv') with chdir(test_dir): completer = PathCompleter(file_filter=file_filter) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] assert ['my.csv'] == result # cleanup shutil.rmtree(test_dir) def test_pathcompleter_get_paths_constrains_path(): # setup: create a test dir with 10 files test_dir = tempfile.mkdtemp() write_test_files(test_dir) # add a subdir with 10 other files with different names subdir = os.path.join(test_dir, 'subdir') os.mkdir(subdir) write_test_files(subdir, 'abcdefghij') get_paths = lambda: ['subdir'] with chdir(test_dir): completer = PathCompleter(get_paths=get_paths) doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) result = [c.text for c in completions] expected = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] assert expected == result # cleanup shutil.rmtree(test_dir) def test_word_completer_static_word_list(): completer = WordCompleter(['abc', 'def', 'aaa']) # Static list on empty input. completions = completer.get_completions(Document(''), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'def', 'aaa'] # Static list on non-empty input. completions = completer.get_completions(Document('a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] completions = completer.get_completions(Document('A'), CompleteEvent()) assert [c.text for c in completions] == [] # Multiple words ending with space. (Accept all options) completions = completer.get_completions(Document('test '), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'def', 'aaa'] # Multiple words. (Check last only.) completions = completer.get_completions(Document('test a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] def test_word_completer_ignore_case(): completer = WordCompleter(['abc', 'def', 'aaa'], ignore_case=True) completions = completer.get_completions(Document('a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] completions = completer.get_completions(Document('A'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] def test_word_completer_match_middle(): completer = WordCompleter(['abc', 'def', 'abca'], match_middle=True) completions = completer.get_completions(Document('bc'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'abca'] def test_word_completer_sentence(): # With sentence=True completer = WordCompleter(['hello world', 'www', 'hello www', 'hello there'], sentence=True) completions = completer.get_completions(Document('hello w'), CompleteEvent()) assert [c.text for c in completions] == ['hello world', 'hello www'] # With sentence=False completer = WordCompleter(['hello world', 'www', 'hello www', 'hello there'], sentence=False) completions = completer.get_completions(Document('hello w'), CompleteEvent()) assert [c.text for c in completions] == ['www'] def test_word_completer_dynamic_word_list(): called = [0] def get_words(): called[0] += 1 return ['abc', 'def', 'aaa'] completer = WordCompleter(get_words) # Dynamic list on empty input. completions = completer.get_completions(Document(''), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'def', 'aaa'] assert called[0] == 1 # Static list on non-empty input. completions = completer.get_completions(Document('a'), CompleteEvent()) assert [c.text for c in completions] == ['abc', 'aaa'] assert called[0] == 2 def test_word_completer_pattern(): # With a pattern which support '.' completer = WordCompleter(['abc', 'a.b.c', 'a.b', 'xyz'], pattern=re.compile(r'^([a-zA-Z0-9_.]+|[^a-zA-Z0-9_.\s]+)')) completions = completer.get_completions(Document('a.'), CompleteEvent()) assert [c.text for c in completions] == ['a.b.c', 'a.b'] # Without pattern completer = WordCompleter(['abc', 'a.b.c', 'a.b', 'xyz']) completions = completer.get_completions(Document('a.'), CompleteEvent()) assert [c.text for c in completions] == [] def test_fuzzy_completer(): collection = [ 'migrations.py', 'django_migrations.py', 'django_admin_log.py', 'api_user.doc', 'user_group.doc', 'users.txt', 'accounts.txt', '123.py', 'test123test.py' ] completer = FuzzyWordCompleter(collection) completions = completer.get_completions(Document('txt'), CompleteEvent()) assert [c.text for c in completions] == ['users.txt', 'accounts.txt'] completions = completer.get_completions(Document('djmi'), CompleteEvent()) assert [c.text for c in completions] == ['django_migrations.py', 'django_admin_log.py'] completions = completer.get_completions(Document('mi'), CompleteEvent()) assert [c.text for c in completions] == ['migrations.py', 'django_migrations.py', 'django_admin_log.py'] completions = completer.get_completions(Document('user'), CompleteEvent()) assert [c.text for c in completions] == ['user_group.doc', 'users.txt', 'api_user.doc'] completions = completer.get_completions(Document('123'), CompleteEvent()) assert [c.text for c in completions] == ['123.py', 'test123test.py'] completions = completer.get_completions(Document('miGr'), CompleteEvent()) assert [c.text for c in completions] == ['migrations.py', 'django_migrations.py',] # Multiple words ending with space. (Accept all options) completions = completer.get_completions(Document('test '), CompleteEvent()) assert [c.text for c in completions] == collection # Multiple words. (Check last only.) completions = completer.get_completions(Document('test txt'), CompleteEvent()) assert [c.text for c in completions] == ['users.txt', 'accounts.txt'] prompt_toolkit-2.0.10/tests/test_document.py0000644000175100017510000000334013545407204022705 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.document import Document @pytest.fixture def document(): return Document( 'line 1\n' + 'line 2\n' + 'line 3\n' + 'line 4\n', len('line 1\n' + 'lin') ) def test_current_char(document): assert document.current_char == 'e' assert document.char_before_cursor == 'n' def test_text_before_cursor(document): assert document.text_before_cursor == 'line 1\nlin' def test_text_after_cursor(document): assert document.text_after_cursor == 'e 2\n' + \ 'line 3\n' + \ 'line 4\n' def test_lines(document): assert document.lines == [ 'line 1', 'line 2', 'line 3', 'line 4', ''] def test_line_count(document): assert document.line_count == 5 def test_current_line_before_cursor(document): assert document.current_line_before_cursor == 'lin' def test_current_line_after_cursor(document): assert document.current_line_after_cursor == 'e 2' def test_current_line(document): assert document.current_line == 'line 2' def test_cursor_position(document): assert document.cursor_position_row == 1 assert document.cursor_position_col == 3 d = Document('', 0) assert d.cursor_position_row == 0 assert d.cursor_position_col == 0 def test_translate_index_to_position(document): pos = document.translate_index_to_position( len('line 1\nline 2\nlin')) assert pos[0] == 2 assert pos[1] == 3 pos = document.translate_index_to_position(0) assert pos == (0, 0) def test_is_cursor_at_the_end(document): assert Document('hello', 5).is_cursor_at_the_end assert not Document('hello', 4).is_cursor_at_the_end prompt_toolkit-2.0.10/tests/test_filter.py0000644000175100017510000000241213545407204022353 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.filters import Always, Condition, Filter, Never, to_filter def test_never(): assert not Never()() def test_always(): assert Always()() def test_invert(): assert not (~Always())() assert (~Never()()) c = ~Condition(lambda: False) assert c() def test_or(): for a in (True, False): for b in (True, False): c1 = Condition(lambda: a) c2 = Condition(lambda: b) c3 = c1 | c2 assert isinstance(c3, Filter) assert c3() == a or b def test_and(): for a in (True, False): for b in (True, False): c1 = Condition(lambda: a) c2 = Condition(lambda: b) c3 = c1 & c2 assert isinstance(c3, Filter) assert c3() == (a and b) def test_to_filter(): f1 = to_filter(True) f2 = to_filter(False) f3 = to_filter(Condition(lambda: True)) f4 = to_filter(Condition(lambda: False)) assert isinstance(f1, Filter) assert isinstance(f2, Filter) assert isinstance(f3, Filter) assert isinstance(f4, Filter) assert f1() assert not f2() assert f3() assert not f4() with pytest.raises(TypeError): to_filter(4) prompt_toolkit-2.0.10/tests/test_formatted_text.py0000644000175100017510000001255513545407022024126 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.formatted_text import ( ANSI, HTML, FormattedText, PygmentsTokens, Template, merge_formatted_text, to_formatted_text, ) from prompt_toolkit.formatted_text.utils import split_lines def test_basic_html(): html = HTML('hello') assert to_formatted_text(html) == [('class:i', 'hello')] html = HTML('hello') assert to_formatted_text(html) == [('class:i,b', 'hello')] html = HTML('helloworldtestafter') assert to_formatted_text(html) == [ ('class:i,b', 'hello'), ('class:i', 'world'), ('class:i,strong', 'test'), ('', 'after'), ] # It's important that `to_formatted_text` returns a `FormattedText` # instance. Otherwise, `print_formatted_text` won't recognise it and will # print a list literal instead. assert isinstance(to_formatted_text(html), FormattedText) def test_html_with_fg_bg(): html = HTML('') assert to_formatted_text(html) == [ ('bg:ansired', 'hello'), ] html = HTML('') assert to_formatted_text(html) == [ ('fg:#ff0000 bg:ansired', 'hello'), ] html = HTML('') assert to_formatted_text(html) == [ ('fg:#ff0000 bg:ansired', 'hello '), ('class:world fg:ansiblue bg:ansired', 'world'), ] def test_ansi_formatting(): value = ANSI('\x1b[32mHe\x1b[45mllo') assert to_formatted_text(value) == [ ('ansigreen', 'H'), ('ansigreen', 'e'), ('ansigreen bg:ansimagenta', 'l'), ('ansigreen bg:ansimagenta', 'l'), ('ansigreen bg:ansimagenta', 'o'), ] # Bold and italic. value = ANSI('\x1b[1mhe\x1b[0mllo') assert to_formatted_text(value) == [ ('bold', 'h'), ('bold', 'e'), ('', 'l'), ('', 'l'), ('', 'o'), ] # Zero width escapes. value = ANSI('ab\001cd\002ef') assert to_formatted_text(value) == [ ('', 'a'), ('', 'b'), ('[ZeroWidthEscape]', 'cd'), ('', 'e'), ('', 'f'), ] assert isinstance(to_formatted_text(value), FormattedText) def test_ansi_256_color(): assert to_formatted_text(ANSI('\x1b[38;5;124mtest')) == [ ('#af0000', 't'), ('#af0000', 'e'), ('#af0000', 's'), ('#af0000', 't') ] def test_interpolation(): value = Template(' {} ').format(HTML('hello')) assert to_formatted_text(value) == [ ('', ' '), ('class:b', 'hello'), ('', ' '), ] value = Template('a{}b{}c').format(HTML('hello'), 'world') assert to_formatted_text(value) == [ ('', 'a'), ('class:b', 'hello'), ('', 'b'), ('', 'world'), ('', 'c'), ] def test_html_interpolation(): # %-style interpolation. value = HTML('%s') % 'hello' assert to_formatted_text(value) == [ ('class:b', 'hello') ] value = HTML('%s') % ('hello', ) assert to_formatted_text(value) == [ ('class:b', 'hello') ] value = HTML('%s%s') % ('hello', 'world') assert to_formatted_text(value) == [ ('class:b', 'hello'), ('class:u', 'world') ] # Format function. value = HTML('{0}{1}').format('hello', 'world') assert to_formatted_text(value) == [ ('class:b', 'hello'), ('class:u', 'world') ] value = HTML('{a}{b}').format(a='hello', b='world') assert to_formatted_text(value) == [ ('class:b', 'hello'), ('class:u', 'world') ] def test_merge_formatted_text(): html1 = HTML('hello') html2 = HTML('world') result = merge_formatted_text([html1, html2]) assert to_formatted_text(result) == [ ('class:u', 'hello'), ('class:b', 'world'), ] def test_pygments_tokens(): text = [ (('A', 'B'), 'hello'), # Token.A.B (('C', 'D', 'E'), 'hello'), # Token.C.D.E ((), 'world'), # Token ] assert to_formatted_text(PygmentsTokens(text)) == [ ('class:pygments.a.b', 'hello'), ('class:pygments.c.d.e', 'hello'), ('class:pygments', 'world'), ] def test_split_lines(): lines = list(split_lines([('class:a', 'line1\nline2\nline3')])) assert lines == [ [('class:a', 'line1')], [('class:a', 'line2')], [('class:a', 'line3')], ] def test_split_lines_2(): lines = list(split_lines([ ('class:a', 'line1'), ('class:b', 'line2\nline3\nline4') ])) assert lines == [ [('class:a', 'line1'), ('class:b', 'line2')], [('class:b', 'line3')], [('class:b', 'line4')], ] def test_split_lines_3(): " Edge cases: inputs ending with newlines. " # -1- lines = list(split_lines([ ('class:a', 'line1\nline2\n') ])) assert lines == [ [('class:a', 'line1')], [('class:a', 'line2')], [('class:a', '')], ] # -2- lines = list(split_lines([ ('class:a', '\n'), ])) assert lines == [ [], [('class:a', '')], ] # -3- lines = list(split_lines([ ('class:a', ''), ])) assert lines == [ [('class:a', '')], ] prompt_toolkit-2.0.10/tests/test_inputstream.py0000644000175100017510000000776213545407204023456 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.input.vt100_parser import Vt100Parser from prompt_toolkit.keys import Keys class _ProcessorMock(object): def __init__(self): self.keys = [] def feed_key(self, key_press): self.keys.append(key_press) @pytest.fixture def processor(): return _ProcessorMock() @pytest.fixture def stream(processor): return Vt100Parser(processor.feed_key) def test_control_keys(processor, stream): stream.feed('\x01\x02\x10') assert len(processor.keys) == 3 assert processor.keys[0].key == Keys.ControlA assert processor.keys[1].key == Keys.ControlB assert processor.keys[2].key == Keys.ControlP assert processor.keys[0].data == '\x01' assert processor.keys[1].data == '\x02' assert processor.keys[2].data == '\x10' def test_arrows(processor, stream): stream.feed('\x1b[A\x1b[B\x1b[C\x1b[D') assert len(processor.keys) == 4 assert processor.keys[0].key == Keys.Up assert processor.keys[1].key == Keys.Down assert processor.keys[2].key == Keys.Right assert processor.keys[3].key == Keys.Left assert processor.keys[0].data == '\x1b[A' assert processor.keys[1].data == '\x1b[B' assert processor.keys[2].data == '\x1b[C' assert processor.keys[3].data == '\x1b[D' def test_escape(processor, stream): stream.feed('\x1bhello') assert len(processor.keys) == 1 + len('hello') assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == 'h' assert processor.keys[0].data == '\x1b' assert processor.keys[1].data == 'h' def test_special_double_keys(processor, stream): stream.feed('\x1b[1;3D') # Should both send escape and left. assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == Keys.Left assert processor.keys[0].data == '\x1b[1;3D' assert processor.keys[1].data == '\x1b[1;3D' def test_flush_1(processor, stream): # Send left key in two parts without flush. stream.feed('\x1b') stream.feed('[D') assert len(processor.keys) == 1 assert processor.keys[0].key == Keys.Left assert processor.keys[0].data == '\x1b[D' def test_flush_2(processor, stream): # Send left key with a 'Flush' in between. # The flush should make sure that we process everything before as-is, # with makes the first part just an escape character instead. stream.feed('\x1b') stream.flush() stream.feed('[D') assert len(processor.keys) == 3 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == '[' assert processor.keys[2].key == 'D' assert processor.keys[0].data == '\x1b' assert processor.keys[1].data == '[' assert processor.keys[2].data == 'D' def test_meta_arrows(processor, stream): stream.feed('\x1b\x1b[D') assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == Keys.Left def test_control_square_close(processor, stream): stream.feed('\x1dC') assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.ControlSquareClose assert processor.keys[1].key == 'C' def test_invalid(processor, stream): # Invalid sequence that has at two characters in common with other # sequences. stream.feed('\x1b[*') assert len(processor.keys) == 3 assert processor.keys[0].key == Keys.Escape assert processor.keys[1].key == '[' assert processor.keys[2].key == '*' def test_cpr_response(processor, stream): stream.feed('a\x1b[40;10Rb') assert len(processor.keys) == 3 assert processor.keys[0].key == 'a' assert processor.keys[1].key == Keys.CPRResponse assert processor.keys[2].key == 'b' def test_cpr_response_2(processor, stream): # Make sure that the newline is not included in the CPR response. stream.feed('\x1b[40;1R\n') assert len(processor.keys) == 2 assert processor.keys[0].key == Keys.CPRResponse assert processor.keys[1].key == Keys.ControlJ prompt_toolkit-2.0.10/tests/test_key_binding.py0000644000175100017510000001347113545407204023357 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.application import Application from prompt_toolkit.application.current import set_app from prompt_toolkit.input.defaults import create_pipe_input from prompt_toolkit.key_binding.key_bindings import KeyBindings from prompt_toolkit.key_binding.key_processor import KeyPress, KeyProcessor from prompt_toolkit.keys import Keys from prompt_toolkit.layout import Layout, Window from prompt_toolkit.output import DummyOutput class Handlers(object): def __init__(self): self.called = [] def __getattr__(self, name): def func(event): self.called.append(name) return func def set_dummy_app(): """ Return a context manager that makes sure that this dummy application is active. This is important, because we need an `Application` with `is_done=False` flag, otherwise no keys will be processed. """ app = Application( layout=Layout(Window()), output=DummyOutput(), input=create_pipe_input()) return set_app(app) @pytest.fixture def handlers(): return Handlers() @pytest.fixture def bindings(handlers): bindings = KeyBindings() bindings.add( Keys.ControlX, Keys.ControlC)(handlers.controlx_controlc) bindings.add(Keys.ControlX)(handlers.control_x) bindings.add(Keys.ControlD)(handlers.control_d) bindings.add( Keys.ControlSquareClose, Keys.Any)(handlers.control_square_close_any) return bindings @pytest.fixture def processor(bindings): return KeyProcessor(bindings) def test_remove_bindings(handlers): with set_dummy_app(): h = handlers.controlx_controlc h2 = handlers.controld # Test passing a handler to the remove() function. bindings = KeyBindings() bindings.add(Keys.ControlX, Keys.ControlC)(h) bindings.add(Keys.ControlD)(h2) assert len(bindings.bindings) == 2 bindings.remove(h) assert len(bindings.bindings) == 1 # Test passing a key sequence to the remove() function. bindings = KeyBindings() bindings.add(Keys.ControlX, Keys.ControlC)(h) bindings.add(Keys.ControlD)(h2) assert len(bindings.bindings) == 2 bindings.remove(Keys.ControlX, Keys.ControlC) assert len(bindings.bindings) == 1 def test_feed_simple(processor, handlers): with set_dummy_app(): processor.feed(KeyPress(Keys.ControlX, '\x18')) processor.feed(KeyPress(Keys.ControlC, '\x03')) processor.process_keys() assert handlers.called == ['controlx_controlc'] def test_feed_several(processor, handlers): with set_dummy_app(): # First an unknown key first. processor.feed(KeyPress(Keys.ControlQ, '')) processor.process_keys() assert handlers.called == [] # Followed by a know key sequence. processor.feed(KeyPress(Keys.ControlX, '')) processor.feed(KeyPress(Keys.ControlC, '')) processor.process_keys() assert handlers.called == ['controlx_controlc'] # Followed by another unknown sequence. processor.feed(KeyPress(Keys.ControlR, '')) processor.feed(KeyPress(Keys.ControlS, '')) # Followed again by a know key sequence. processor.feed(KeyPress(Keys.ControlD, '')) processor.process_keys() assert handlers.called == ['controlx_controlc', 'control_d'] def test_control_square_closed_any(processor, handlers): with set_dummy_app(): processor.feed(KeyPress(Keys.ControlSquareClose, '')) processor.feed(KeyPress('C', 'C')) processor.process_keys() assert handlers.called == ['control_square_close_any'] def test_common_prefix(processor, handlers): with set_dummy_app(): # Sending Control_X should not yet do anything, because there is # another sequence starting with that as well. processor.feed(KeyPress(Keys.ControlX, '')) processor.process_keys() assert handlers.called == [] # When another key is pressed, we know that we did not meant the longer # "ControlX ControlC" sequence and the callbacks are called. processor.feed(KeyPress(Keys.ControlD, '')) processor.process_keys() assert handlers.called == ['control_x', 'control_d'] def test_previous_key_sequence(processor): """ test whether we receive the correct previous_key_sequence. """ with set_dummy_app(): events = [] def handler(event): events.append(event) # Build registry. registry = KeyBindings() registry.add('a', 'a')(handler) registry.add('b', 'b')(handler) processor = KeyProcessor(registry) # Create processor and feed keys. processor.feed(KeyPress('a', 'a')) processor.feed(KeyPress('a', 'a')) processor.feed(KeyPress('b', 'b')) processor.feed(KeyPress('b', 'b')) processor.process_keys() # Test. assert len(events) == 2 assert len(events[0].key_sequence) == 2 assert events[0].key_sequence[0].key == 'a' assert events[0].key_sequence[0].data == 'a' assert events[0].key_sequence[1].key == 'a' assert events[0].key_sequence[1].data == 'a' assert events[0].previous_key_sequence == [] assert len(events[1].key_sequence) == 2 assert events[1].key_sequence[0].key == 'b' assert events[1].key_sequence[0].data == 'b' assert events[1].key_sequence[1].key == 'b' assert events[1].key_sequence[1].data == 'b' assert len(events[1].previous_key_sequence) == 2 assert events[1].previous_key_sequence[0].key == 'a' assert events[1].previous_key_sequence[0].data == 'a' assert events[1].previous_key_sequence[1].key == 'a' assert events[1].previous_key_sequence[1].data == 'a' prompt_toolkit-2.0.10/tests/test_layout.py0000644000175100017510000000307413545407204022410 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.layout import InvalidLayoutError, Layout from prompt_toolkit.layout.containers import HSplit, VSplit, Window from prompt_toolkit.layout.controls import BufferControl def test_layout_class(): c1 = BufferControl() c2 = BufferControl() c3 = BufferControl() win1 = Window(content=c1) win2 = Window(content=c2) win3 = Window(content=c3) layout = Layout(container=VSplit([ HSplit([ win1, win2 ]), win3 ])) # Listing of windows/controls. assert list(layout.find_all_windows()) == [win1, win2, win3] assert list(layout.find_all_controls()) == [c1, c2, c3] # Focusing something. layout.focus(c1) assert layout.has_focus(c1) assert layout.has_focus(win1) assert layout.current_control == c1 assert layout.previous_control == c1 layout.focus(c2) assert layout.has_focus(c2) assert layout.has_focus(win2) assert layout.current_control == c2 assert layout.previous_control == c1 layout.focus(win3) assert layout.has_focus(c3) assert layout.has_focus(win3) assert layout.current_control == c3 assert layout.previous_control == c2 # Pop focus. This should focus the previous control again. layout.focus_last() assert layout.has_focus(c2) assert layout.has_focus(win2) assert layout.current_control == c2 assert layout.previous_control == c1 def test_create_invalid_layout(): with pytest.raises(InvalidLayoutError): Layout(HSplit([])) prompt_toolkit-2.0.10/tests/test_print_formatted_text.py0000644000175100017510000000430513545407204025336 0ustar jonathanjonathan00000000000000""" Test the `print` function. """ from __future__ import print_function, unicode_literals import pytest from prompt_toolkit import print_formatted_text as pt_print from prompt_toolkit.formatted_text import ( HTML, FormattedText, to_formatted_text, ) from prompt_toolkit.styles import Style from prompt_toolkit.utils import is_windows class _Capture: " Emulate an stdout object. " encoding = 'utf-8' def __init__(self): self._data = [] def write(self, data): self._data.append(data) @property def data(self): return b''.join(self._data) def flush(self): pass def isatty(self): return True def fileno(self): # File descriptor is not used for printing formatted text. # (It is only needed for getting the terminal size.) return -1 @pytest.mark.skipif( is_windows(), reason="Doesn't run on Windows yet.") def test_print_formatted_text(): f = _Capture() pt_print([('', 'hello'), ('', 'world')], file=f) assert b'hello' in f.data assert b'world' in f.data @pytest.mark.skipif( is_windows(), reason="Doesn't run on Windows yet.") def test_print_formatted_text_backslash_r(): f = _Capture() pt_print('hello\r\n', file=f) assert b'hello' in f.data @pytest.mark.skipif( is_windows(), reason="Doesn't run on Windows yet.") def test_with_style(): f = _Capture() style = Style.from_dict({ 'hello': '#ff0066', 'world': '#44ff44 italic', }) tokens = FormattedText([ ('class:hello', 'Hello '), ('class:world', 'world'), ]) pt_print(tokens, style=style, file=f) assert b'\x1b[0;38;5;197mHello' in f.data assert b'\x1b[0;38;5;83;3mworld' in f.data @pytest.mark.skipif( is_windows(), reason="Doesn't run on Windows yet.") def test_with_style(): """ Text `print_formatted_text` with `HTML` wrapped in `to_formatted_text`. """ f = _Capture() html = HTML('hello world') formatted_text = to_formatted_text(html, style='class:myhtml') pt_print(formatted_text, file=f) assert f.data == \ b'\x1b[0m\x1b[?7h\x1b[0;32mhello\x1b[0m \x1b[0;1mworld\x1b[0m\r\n\x1b[0m' prompt_toolkit-2.0.10/tests/test_regular_languages.py0000644000175100017510000000627013545407022024561 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.completion import CompleteEvent, Completer, Completion from prompt_toolkit.contrib.regular_languages import compile from prompt_toolkit.contrib.regular_languages.compiler import Match, Variables from prompt_toolkit.contrib.regular_languages.completion import ( GrammarCompleter, ) from prompt_toolkit.document import Document def test_simple_match(): g = compile('hello|world') m = g.match('hello') assert isinstance(m, Match) m = g.match('world') assert isinstance(m, Match) m = g.match('somethingelse') assert m is None def test_variable_varname(): """ Test `Variable` with varname. """ g = compile('((?Phello|world)|test)') m = g.match('hello') variables = m.variables() assert isinstance(variables, Variables) assert variables.get('varname') == 'hello' assert variables['varname'] == 'hello' m = g.match('world') variables = m.variables() assert isinstance(variables, Variables) assert variables.get('varname') == 'world' assert variables['varname'] == 'world' m = g.match('test') variables = m.variables() assert isinstance(variables, Variables) assert variables.get('varname') is None assert variables['varname'] is None def test_prefix(): """ Test `match_prefix`. """ g = compile(r'(hello\ world|something\ else)') m = g.match_prefix('hello world') assert isinstance(m, Match) m = g.match_prefix('he') assert isinstance(m, Match) m = g.match_prefix('') assert isinstance(m, Match) m = g.match_prefix('som') assert isinstance(m, Match) m = g.match_prefix('hello wor') assert isinstance(m, Match) m = g.match_prefix('no-match') assert m.trailing_input().start == 0 assert m.trailing_input().stop == len('no-match') m = g.match_prefix('hellotest') assert m.trailing_input().start == len('hello') assert m.trailing_input().stop == len('hellotest') def test_completer(): class completer1(Completer): def get_completions(self, document, complete_event): yield Completion( 'before-%s-after' % document.text, -len(document.text)) yield Completion( 'before-%s-after-B' % document.text, -len(document.text)) class completer2(Completer): def get_completions(self, document, complete_event): yield Completion( 'before2-%s-after2' % document.text, -len(document.text)) yield Completion( 'before2-%s-after2-B' % document.text, -len(document.text)) # Create grammar. "var1" + "whitespace" + "var2" g = compile(r'(?P[a-z]*) \s+ (?P[a-z]*)') # Test 'get_completions()' completer = GrammarCompleter( g, {'var1': completer1(), 'var2': completer2()}) completions = list(completer.get_completions( Document('abc def', len('abc def')), CompleteEvent())) assert len(completions) == 2 assert completions[0].text == 'before2-def-after2' assert completions[0].start_position == -3 assert completions[1].text == 'before2-def-after2-B' assert completions[1].start_position == -3 prompt_toolkit-2.0.10/tests/test_shortcuts.py0000644000175100017510000000323213545407022023123 0ustar jonathanjonathan00000000000000from prompt_toolkit.shortcuts.prompt import _split_multiline_prompt def test_split_multiline_prompt(): # Test 1: no newlines: tokens = [('class:testclass', 'ab')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda: tokens) assert has_before_tokens() is False assert before() == [] assert first_input_line() == [ ('class:testclass', 'a'), ('class:testclass', 'b'), ] # Test 1: multiple lines. tokens = [('class:testclass', 'ab\ncd\nef')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda: tokens) assert has_before_tokens() is True assert before() == [ ('class:testclass', 'a'), ('class:testclass', 'b'), ('class:testclass', '\n'), ('class:testclass', 'c'), ('class:testclass', 'd'), ] assert first_input_line() == [ ('class:testclass', 'e'), ('class:testclass', 'f'), ] # Edge case 1: starting with a newline. tokens = [('class:testclass', '\nab')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda: tokens) assert has_before_tokens() is True assert before() == [] assert first_input_line() == [ ('class:testclass', 'a'), ('class:testclass', 'b') ] # Edge case 2: starting with two newlines. tokens = [('class:testclass', '\n\nab')] has_before_tokens, before, first_input_line = _split_multiline_prompt(lambda: tokens) assert has_before_tokens() is True assert before() == [('class:testclass', '\n')] assert first_input_line() == [ ('class:testclass', 'a'), ('class:testclass', 'b') ] prompt_toolkit-2.0.10/tests/test_style.py0000644000175100017510000001271513545407022022233 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.styles import ( Attrs, Style, SwapLightAndDarkStyleTransformation, ) def test_style_from_dict(): style = Style.from_dict({ 'a': '#ff0000 bold underline italic', 'b': 'bg:#00ff00 blink reverse', }) # Lookup of class:a. expected = Attrs(color='ff0000', bgcolor='', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a') == expected # Lookup of class:b. expected = Attrs(color='', bgcolor='00ff00', bold=False, underline=False, italic=False, blink=True, reverse=True, hidden=False) assert style.get_attrs_for_style_str('class:b') == expected # Test inline style. expected = Attrs(color='ff0000', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('#ff0000') == expected # Combine class name and inline style (Whatever is defined later gets priority.) expected = Attrs(color='00ff00', bgcolor='', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a #00ff00') == expected expected = Attrs(color='ff0000', bgcolor='', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('#00ff00 class:a') == expected def test_class_combinations_1(): # In this case, our style has both class 'a' and 'b'. # Given that the style for 'a b' is defined at the end, that one is used. style = Style([ ('a', '#0000ff'), ('b', '#00ff00'), ('a b', '#ff0000'), ]) expected = Attrs(color='ff0000', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a class:b') == expected assert style.get_attrs_for_style_str('class:a,b') == expected assert style.get_attrs_for_style_str('class:a,b,c') == expected # Changing the order shouldn't matter. assert style.get_attrs_for_style_str('class:b class:a') == expected assert style.get_attrs_for_style_str('class:b,a') == expected def test_class_combinations_2(): # In this case, our style has both class 'a' and 'b'. # The style that is defined the latest get priority. style = Style([ ('a b', '#ff0000'), ('b', '#00ff00'), ('a', '#0000ff'), ]) expected = Attrs(color='00ff00', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a class:b') == expected assert style.get_attrs_for_style_str('class:a,b') == expected assert style.get_attrs_for_style_str('class:a,b,c') == expected # Defining 'a' latest should give priority to 'a'. expected = Attrs(color='0000ff', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:b class:a') == expected assert style.get_attrs_for_style_str('class:b,a') == expected def test_substyles(): style = Style([ ('a.b', '#ff0000 bold'), ('a', '#0000ff'), ('b', '#00ff00'), ('b.c', '#0000ff italic'), ]) # Starting with a.* expected = Attrs(color='0000ff', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a') == expected expected = Attrs(color='ff0000', bgcolor='', bold=True, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:a.b') == expected assert style.get_attrs_for_style_str('class:a.b.c') == expected # Starting with b.* expected = Attrs(color='00ff00', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:b') == expected assert style.get_attrs_for_style_str('class:b.a') == expected expected = Attrs(color='0000ff', bgcolor='', bold=False, underline=False, italic=True, blink=False, reverse=False, hidden=False) assert style.get_attrs_for_style_str('class:b.c') == expected assert style.get_attrs_for_style_str('class:b.c.d') == expected def test_swap_light_and_dark_style_transformation(): transformation = SwapLightAndDarkStyleTransformation() # Test with 6 digit hex colors. before = Attrs(color='440000', bgcolor='888844', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) after = Attrs(color='ffbbbb', bgcolor='bbbb76', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert transformation.transform_attrs(before) == after # Test with ANSI colors. before = Attrs(color='ansired', bgcolor='ansiblack', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) after = Attrs(color='ansibrightred', bgcolor='ansiwhite', bold=True, underline=True, italic=True, blink=False, reverse=False, hidden=False) assert transformation.transform_attrs(before) == after prompt_toolkit-2.0.10/tests/test_style_transformation.py0000644000175100017510000000263513545407204025363 0ustar jonathanjonathan00000000000000import pytest from prompt_toolkit.styles import AdjustBrightnessStyleTransformation, Attrs @pytest.fixture def default_attrs(): return Attrs( color='', bgcolor='', bold=False, underline=False, italic=False, blink=False, reverse=False, hidden=False) def test_adjust_brightness_style_transformation(default_attrs): tr = AdjustBrightnessStyleTransformation(.5, 1.0) attrs = tr.transform_attrs(default_attrs._replace(color='ff0000')) assert attrs.color == 'ff7f7f' attrs = tr.transform_attrs(default_attrs._replace(color='00ffaa')) assert attrs.color == '7fffd4' # When a background color is given, nothing should change. attrs = tr.transform_attrs(default_attrs._replace(color='00ffaa', bgcolor='white')) assert attrs.color == '00ffaa' # Test ansi colors. attrs = tr.transform_attrs(default_attrs._replace(color='ansiblue')) assert attrs.color == '6666ff' # Test 'ansidefault'. This shouldn't change. attrs = tr.transform_attrs(default_attrs._replace(color='ansidefault')) assert attrs.color == 'ansidefault' # When 0 and 1 are given, don't do any style transformation. tr2 = AdjustBrightnessStyleTransformation(0, 1) attrs = tr2.transform_attrs(default_attrs._replace(color='ansiblue')) assert attrs.color == 'ansiblue' attrs = tr2.transform_attrs(default_attrs._replace(color='00ffaa')) assert attrs.color == '00ffaa' prompt_toolkit-2.0.10/tests/test_utils.py0000644000175100017510000000252613545407204022234 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import itertools import pytest from prompt_toolkit.utils import take_using_weights def test_using_weights(): def take(generator, count): return list(itertools.islice(generator, 0, count)) # Check distribution. data = take(take_using_weights(['A', 'B', 'C'], [5, 10, 20]), 35) assert data.count('A') == 5 assert data.count('B') == 10 assert data.count('C') == 20 assert data == [ 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C', 'A', 'B', 'C', 'C', 'B', 'C', 'C'] # Another order. data = take(take_using_weights(['A', 'B', 'C'], [20, 10, 5]), 35) assert data.count('A') == 20 assert data.count('B') == 10 assert data.count('C') == 5 # Bigger numbers. data = take(take_using_weights(['A', 'B', 'C'], [20, 10, 5]), 70) assert data.count('A') == 40 assert data.count('B') == 20 assert data.count('C') == 10 # Negative numbers. data = take(take_using_weights(['A', 'B', 'C'], [-20, 10, 0]), 70) assert data.count('A') == 0 assert data.count('B') == 70 assert data.count('C') == 0 # All zero-weight items. with pytest.raises(ValueError): take(take_using_weights(['A', 'B', 'C'], [0, 0, 0]), 70) prompt_toolkit-2.0.10/tests/test_vt100_output.py0000644000175100017510000000127213545407022023361 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.output.vt100 import _get_closest_ansi_color def test_get_closest_ansi_color(): # White assert _get_closest_ansi_color(255, 255, 255) == 'ansiwhite' assert _get_closest_ansi_color(250, 250, 250) == 'ansiwhite' # Black assert _get_closest_ansi_color(0, 0, 0) == 'ansiblack' assert _get_closest_ansi_color(5, 5, 5) == 'ansiblack' # Green assert _get_closest_ansi_color(0, 255, 0) == 'ansibrightgreen' assert _get_closest_ansi_color(10, 255, 0) == 'ansibrightgreen' assert _get_closest_ansi_color(0, 255, 10) == 'ansibrightgreen' assert _get_closest_ansi_color(220, 220, 100) == 'ansiyellow' prompt_toolkit-2.0.10/tests/test_yank_nth_arg.py0000644000175100017510000000404513545407204023536 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import pytest from prompt_toolkit.buffer import Buffer from prompt_toolkit.history import InMemoryHistory @pytest.fixture def _history(): " Prefilled history. " history = InMemoryHistory() history.append_string('alpha beta gamma delta') history.append_string('one two three four') return history # Test yank_last_arg. def test_empty_history(): buf = Buffer() buf.yank_last_arg() assert buf.document.current_line == '' def test_simple_search(_history): buff = Buffer(history=_history) buff.yank_last_arg() assert buff.document.current_line == 'four' def test_simple_search_with_quotes(_history): _history.append_string("""one two "three 'x' four"\n""") buff = Buffer(history=_history) buff.yank_last_arg() assert buff.document.current_line == '''"three 'x' four"''' def test_simple_search_with_arg(_history): buff = Buffer(history=_history) buff.yank_last_arg(n=2) assert buff.document.current_line == 'three' def test_simple_search_with_arg_out_of_bounds(_history): buff = Buffer(history=_history) buff.yank_last_arg(n=8) assert buff.document.current_line == '' def test_repeated_search(_history): buff = Buffer(history=_history) buff.yank_last_arg() buff.yank_last_arg() assert buff.document.current_line == 'delta' def test_repeated_search_with_wraparound(_history): buff = Buffer(history=_history) buff.yank_last_arg() buff.yank_last_arg() buff.yank_last_arg() assert buff.document.current_line == 'four' # Test yank_last_arg. def test_yank_nth_arg(_history): buff = Buffer(history=_history) buff.yank_nth_arg() assert buff.document.current_line == 'two' def test_repeated_yank_nth_arg(_history): buff = Buffer(history=_history) buff.yank_nth_arg() buff.yank_nth_arg() assert buff.document.current_line == 'beta' def test_yank_nth_arg_with_arg(_history): buff = Buffer(history=_history) buff.yank_nth_arg(n=2) assert buff.document.current_line == 'three'
      ', '', '