prompt_toolkit-0.57/0000755000175000017500000000000012642647210016165 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/CHANGELOG0000644000175000017500000005707612642647131017420 0ustar jonathanjonathan00000000000000CHANGELOG ========= About the version numbering: As long as the version number starts with a zero, we allow backwards incompatible changes between each release. This is required order to move forward. However, we do our best to keep it to a minimum and ensure backwards-compatibility in the most used public APIs. When the library stabilizes, which is hopefully very soon, we will switch to a .. style of numbering. 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 desactivation. 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 scolling 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 transparancy. (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-compatibilty. - BufferControl no longer has a `show_line_numbers` argument. Pass a `NumberredMargin` 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 paramter 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 directiories 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 hight 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_srcoll 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 hight 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.) Overal 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-0.57/AUTHORS.rst0000644000175000000000000000022412556317626017217 0ustar jonathanroot00000000000000Authors ======= Creator ------- Jonathan Slenders Contributors ------------ - Amjith Ramanujam prompt_toolkit-0.57/setup.cfg0000644000175000017500000000007312642647210020006 0ustar jonathanjonathan00000000000000[egg_info] tag_svn_revision = 0 tag_date = 0 tag_build = prompt_toolkit-0.57/prompt_toolkit.egg-info/0000755000175000017500000000000012642647210022745 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit.egg-info/top_level.txt0000644000175000000000000000001712642647210024641 0ustar jonathanroot00000000000000prompt_toolkit prompt_toolkit-0.57/prompt_toolkit.egg-info/SOURCES.txt0000644000175000000000000001172312642647210024001 0ustar jonathanroot00000000000000AUTHORS.rst CHANGELOG LICENSE MANIFEST.in README.rst TODO.rst setup.py examples/abortaction.retry.py examples/asyncio-prompt-simplified.py examples/asyncio-prompt.py examples/auto-suggestion.py examples/autocompletion-enter.py examples/autocompletion.py examples/autocorrection.py examples/bottom-toolbar.py examples/clock-input.py examples/colored-prompt.py examples/custom-key-binding.py examples/full-screen-layout.py examples/get-input-vi-mode.py examples/get-input-with-default.py examples/get-input.py examples/get-multiline-input.py examples/get-password-with-toggle-display-shortcut.py examples/get-password.py examples/html-input.py examples/input-validation.py examples/inputhook.py examples/mouse-support.py examples/multi-column-autocompletion-with-meta.py examples/multi-column-autocompletion.py examples/multiline-prompt.py examples/no-wrapping.py examples/patch-stdout.py examples/persistent-history.py examples/regular-language.py examples/switch-between-vi-emacs.py examples/system-autocompletion.py examples/system-clipboard-integration.py examples/system-prompt.py examples/telnet.py examples/terminal-title.py examples/up-arrow-partial-string-matching.py examples/tutorial/sqlite-cli.py prompt_toolkit/__init__.py prompt_toolkit/application.py prompt_toolkit/auto_suggest.py prompt_toolkit/buffer.py prompt_toolkit/buffer_mapping.py prompt_toolkit/completion.py prompt_toolkit/document.py prompt_toolkit/enums.py prompt_toolkit/history.py prompt_toolkit/input.py prompt_toolkit/interface.py prompt_toolkit/keys.py prompt_toolkit/mouse_events.py prompt_toolkit/output.py prompt_toolkit/reactive.py prompt_toolkit/renderer.py prompt_toolkit/search_state.py prompt_toolkit/selection.py prompt_toolkit/shortcuts.py prompt_toolkit/styles.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/clipboard/__init__.py prompt_toolkit/clipboard/base.py prompt_toolkit/clipboard/in_memory.py prompt_toolkit/clipboard/pyperclip.py prompt_toolkit/contrib/__init__.py prompt_toolkit/contrib/completers/__init__.py prompt_toolkit/contrib/completers/base.py prompt_toolkit/contrib/completers/filesystem.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/application.py prompt_toolkit/contrib/telnet/log.py prompt_toolkit/contrib/telnet/protocol.py prompt_toolkit/contrib/telnet/server.py prompt_toolkit/contrib/validators/__init__.py prompt_toolkit/contrib/validators/base.py prompt_toolkit/eventloop/__init__.py prompt_toolkit/eventloop/asyncio_base.py prompt_toolkit/eventloop/asyncio_posix.py prompt_toolkit/eventloop/asyncio_win32.py prompt_toolkit/eventloop/base.py prompt_toolkit/eventloop/callbacks.py prompt_toolkit/eventloop/inputhook.py prompt_toolkit/eventloop/posix.py prompt_toolkit/eventloop/posix_utils.py prompt_toolkit/eventloop/utils.py prompt_toolkit/eventloop/win32.py prompt_toolkit/filters/__init__.py prompt_toolkit/filters/base.py prompt_toolkit/filters/cli.py prompt_toolkit/filters/types.py prompt_toolkit/filters/utils.py prompt_toolkit/key_binding/__init__.py prompt_toolkit/key_binding/input_processor.py prompt_toolkit/key_binding/manager.py prompt_toolkit/key_binding/registry.py prompt_toolkit/key_binding/vi_state.py prompt_toolkit/key_binding/bindings/__init__.py prompt_toolkit/key_binding/bindings/basic.py prompt_toolkit/key_binding/bindings/emacs.py prompt_toolkit/key_binding/bindings/scroll.py prompt_toolkit/key_binding/bindings/utils.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/highlighters.py prompt_toolkit/layout/lexers.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/prompt.py prompt_toolkit/layout/screen.py prompt_toolkit/layout/toolbars.py prompt_toolkit/layout/utils.py prompt_toolkit/terminal/__init__.py prompt_toolkit/terminal/conemu_output.py prompt_toolkit/terminal/vt100_input.py prompt_toolkit/terminal/vt100_output.py prompt_toolkit/terminal/win32_input.py prompt_toolkit/terminal/win32_output.py tests/contrib_tests.py tests/run_tests.py tests/buffer_tests/__init__.py tests/document_tests/__init__.py tests/inputstream_tests/__init__.py tests/key_binding_tests/__init__.py tests/layout_tests/__init__.py tests/regular_languages_tests/__init__.py tests/screen_tests/__init__.py tests/utils_tests/__init__.pyprompt_toolkit-0.57/prompt_toolkit.egg-info/dependency_links.txt0000644000175000000000000000000112642647210026157 0ustar jonathanroot00000000000000 prompt_toolkit-0.57/prompt_toolkit.egg-info/PKG-INFO0000644000175000000000000001655512642647210023222 0ustar jonathanroot00000000000000Metadata-Version: 1.0 Name: prompt-toolkit Version: 0.57 Summary: Library for building powerful interactive command lines in Python Home-page: https://github.com/jonathanslenders/python-prompt-toolkit Author: Jonathan Slenders Author-email: UNKNOWN License: LICENSE.txt Description: Python Prompt Toolkit ===================== |Build Status| |PyPI| ``prompt_toolkit`` is a library for building powerful interactive command lines and terminal applications in Python. Read the `documentation on readthedocs `_. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt-toolkit. .. image :: https://github.com/jonathanslenders/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.) - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.4. - 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. - Code written with love. - Runs on Linux, OS X, OpenBSD and Windows systems. 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, you need to add ``from __future__ import unicode_literals`` to the above example. All strings are expected to be unicode strings. Projects using prompt-toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql 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. Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure 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 focussing 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 initialisation). 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/jonathanslenders/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit# .. |PyPI| image:: https://pypip.in/version/prompt-toolkit/badge.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version Platform: UNKNOWN prompt_toolkit-0.57/prompt_toolkit.egg-info/requires.txt0000644000175000000000000000003412642647210024506 0ustar jonathanroot00000000000000pygments six>=1.9.0 wcwidth prompt_toolkit-0.57/MANIFEST.in0000644000175000017500000000020512620263107017713 0ustar jonathanjonathan00000000000000include *rst LICENSE CHANGELOG MANIFEST.in recursive-include examples *.py recursive-include tests *.py prune examples/sample?/build prompt_toolkit-0.57/setup.py0000644000175000017500000000127512642646276017717 0ustar jonathanjonathan00000000000000#!/usr/bin/env python import os from setuptools import setup, find_packages long_description = open( os.path.join( os.path.dirname(__file__), 'README.rst' ) ).read() version = '0.57' # Don't forget to update in prompt_toolkit.__init__! setup( name='prompt_toolkit', author='Jonathan Slenders', version=version, license='LICENSE.txt', 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 = [ 'pygments', 'six>=1.9.0', 'wcwidth', ], ) prompt_toolkit-0.57/examples/0000755000175000017500000000000012642647210020003 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/examples/up-arrow-partial-string-matching.py0000755000175000017500000000221312621676425026667 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 unicode_literals, print_function from prompt_toolkit import prompt from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.interface import AbortAction def main(): # Create some history first. (Easy for testing.) history = InMemoryHistory() history.append('import os') history.append('print("hello")') history.append('print("world")') history.append('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() text = prompt('Say something: ', history=history, enable_history_search=True, on_abort=AbortAction.RETRY) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/custom-key-binding.py0000755000175000017500000000356412604630260024073 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.key_binding.manager import KeyBindingManager from prompt_toolkit.keys import Keys def main(): # We start with a `KeyBindingManager` instance, because this will already # nicely load all the default key bindings. key_bindings_manager = KeyBindingManager.for_prompt() # Add our own key binding to the registry of the key bindings manager. @key_bindings_manager.registry.add_binding(Keys.F4) def _(event): """ When F4 has been pressed. Insert "hello world" as text. """ event.cli.current_buffer.insert_text('hello world') @key_bindings_manager.registry.add_binding('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. """ event.cli.current_buffer.insert_text('z') @key_bindings_manager.registry.add_binding(Keys.ControlT) 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') event.cli.run_in_terminal(print_hello) # Read input. print('Press F4 to insert "hello world", type "xy" to insert "z":') text = prompt('> ', key_bindings_registry=key_bindings_manager.registry) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/get-input-with-default.py0000755000175000017500000000057112604630260024665 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 from prompt_toolkit import prompt import getpass if __name__ == '__main__': answer = prompt('What is your name: ', default='%s' % getpass.getuser()) print('You said: %s' % answer) prompt_toolkit-0.57/examples/get-multiline-input.py0000755000175000017500000000044712604313374024300 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': print('Press [Meta+Enter] or [Esc] followed by [Enter] to accept input.') answer = prompt('Multiline input: ', multiline=True) print('You said: %s' % answer) prompt_toolkit-0.57/examples/patch-stdout.py0000755000175000017500000000154312611026256022777 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 from prompt_toolkit import prompt import threading import time 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) threading.Thread(target=thread).start() # Now read the input. The print statements of the other thread # should not disturb anything. result = prompt('Say something: ', patch_stdout=True) print('You said: %s' % result) # Stop thrad. running = False if __name__ == '__main__': main() prompt_toolkit-0.57/examples/multi-column-autocompletion-with-meta.py0000755000175000017500000000201712604313374027741 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Autocompletion example that shows meta-information alongside the completions. """ from __future__ import unicode_literals from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit import prompt animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', '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, display_completions_in_columns=True) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/telnet.py0000755000175000000000000000327412605366307021031 0ustar jonathanroot00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit.contrib.telnet.application import TelnetApplication from prompt_toolkit.contrib.telnet.server import TelnetServer from prompt_toolkit.shortcuts import create_prompt_application from prompt_toolkit.application import AbortAction from pygments.lexers import HtmlLexer import logging # Set up logging logging.basicConfig() logging.getLogger().setLevel(logging.INFO) class ExampleApplication(TelnetApplication): def client_connected(self, telnet_connection): # When a client is connected, erase the screen from the client and say # Hello. telnet_connection.erase_screen() telnet_connection.send('Welcome!\n') # Set CommandLineInterface. animal_completer = WordCompleter(['alligator', 'ant']) telnet_connection.set_application( create_prompt_application(message='Say something: ', lexer=HtmlLexer, completer=animal_completer, on_abort=AbortAction.RETRY), self.handle_command) def handle_command(self, telnet_connection, document): # When the client enters a command, just reply. if document.text == 'exit': telnet_connection.close() else: telnet_connection.send('You said: %s\n\n' % document.text) def client_leaving(self, telnet_connection): # Say 'bye' when the client quits. telnet_connection.send('Bye.\n') if __name__ == '__main__': TelnetServer(application=ExampleApplication(), port=2323).run() prompt_toolkit-0.57/examples/terminal-title.py0000755000175000017500000000043212604313374023310 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': def get_title(): return 'This is the title' answer = prompt('Give me some input: ', get_title=get_title) print('You said: %s' % answer) prompt_toolkit-0.57/examples/system-autocompletion.py0000644000175000017500000000055212605347757024757 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of autocompletion using filenames. """ from __future__ import unicode_literals from prompt_toolkit.contrib.completers.system import SystemCompleter from prompt_toolkit import prompt def main(): text = prompt('Shell: ', completer=SystemCompleter()) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/input-validation.py0000755000175000017500000000123512604313374023647 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of input validation. """ from __future__ import unicode_literals from prompt_toolkit.validation import Validator, ValidationError from prompt_toolkit import prompt class EmailValidator(Validator): def validate(self, document): if '@' not in document.text: raise ValidationError(message='Not a valid e-mail address (Does not contain an @).', cursor_position=len(document.text)) # Move cursor to end of input. def main(): text = prompt('Enter e-mail address: ', validator=EmailValidator()) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/autocompletion-enter.py0000755000175000017500000000234612606067661024550 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a prompt with autocompletion, where pressing the Enter key accepts the completion. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit.filters import HasCompletions from prompt_toolkit.key_binding.manager import KeyBindingManager from prompt_toolkit.keys import Keys animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', 'dove', 'duck', 'eagle', 'elephant', 'fish', 'goat', 'gorilla', 'kangoroo', 'leopard', 'lion', 'mouse', 'rabbit', 'rat', 'snake', 'spider', 'turkey', 'turtle', ], ignore_case=True) def main(): key_bindings_manager = KeyBindingManager.for_prompt() @key_bindings_manager.registry.add_binding(Keys.ControlJ, filter=HasCompletions()) def _(event): event.current_buffer.complete_state = None text = prompt('Give some animals: ', completer=animal_completer, key_bindings_registry=key_bindings_manager.registry) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/inputhook.py0000755000175000017500000000450612604313374022404 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 prompt_toolkit.shortcuts import prompt, create_eventloop from pygments.lexers import PythonLexer import gtk, gobject 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 say `patch_stdout=True`, because clicking the button will print # something; and that should print nicely 'above' the input line. result = prompt('Python >>> ', eventloop=create_eventloop(inputhook=inputhook), lexer=PythonLexer, patch_stdout=True) print('You said: %s' % result) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/autocompletion.py0000755000175000017500000000206712641135342023424 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. - The second Tab press 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.contrib.completers import WordCompleter from prompt_toolkit import prompt animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', 'dove', 'duck', 'eagle', 'elephant', 'fish', 'goat', 'gorilla', 'kangoroo', 'leopard', 'lion', 'mouse', 'rabbit', 'rat', 'snake', 'spider', 'turkey', 'turtle', ], ignore_case=True) def main(): text = prompt('Give some animals: ', completer=animal_completer) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/html-input.py0000755000175000017500000000062612604313374022464 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example of a syntax-highlighted HTML input line. """ from __future__ import unicode_literals from pygments.lexers import HtmlLexer from prompt_toolkit import prompt from prompt_toolkit.layout.lexers import PygmentsLexer def main(): text = prompt('Enter HTML: ', lexer=PygmentsLexer(HtmlLexer)) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/get-input.py0000755000175000017500000000031412604341340022263 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: ') print('You said: %s' % answer) prompt_toolkit-0.57/examples/persistent-history.py0000755000175000017500000000102412604313374024253 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 prompt from prompt_toolkit.history import FileHistory def main(): our_history = FileHistory('.example-history-file') text = prompt('Say something: ', history=our_history) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/multi-column-autocompletion.py0000755000175000017500000000165312604313374026051 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.contrib.completers import WordCompleter from prompt_toolkit import prompt animal_completer = WordCompleter([ 'alligator', 'ant', 'ape', 'bat', 'bear', 'beaver', 'bee', 'bison', 'butterfly', 'cat', 'chicken', 'crocodile', 'dinosaur', 'dog', 'dolphine', 'dove', 'duck', 'eagle', 'elephant', 'fish', 'goat', 'gorilla', 'kangoroo', 'leopard', 'lion', 'mouse', 'rabbit', 'rat', 'snake', 'spider', 'turkey', 'turtle', ], ignore_case=True) def main(): text = prompt('Give some animals: ', completer=animal_completer, display_completions_in_columns=True) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/bottom-toolbar.py0000755000175000017500000000120412620262265023320 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Simple example showing a bottom toolbar. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.styles import PygmentsStyle from pygments.token import Token test_style = PygmentsStyle.from_defaults({ Token.Toolbar: '#ffffff bg:#333333', }) def main(): def get_bottom_toolbar_tokens(cli): return [(Token.Toolbar, ' This is a toolbar. ')] text = prompt('Say something: ', get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, style=test_style) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/switch-between-vi-emacs.py0000644000175000017500000000203612607065567025022 0ustar jonathanjonathan00000000000000from prompt_toolkit import prompt from prompt_toolkit.filters import Condition from prompt_toolkit.key_binding.manager import KeyBindingManager from prompt_toolkit.keys import Keys from pygments.token import Token def run(): vi_mode_enabled = False # Create a set of key bindings that have Vi mode enabled if the # ``vi_mode_enabled`` is True.. manager = KeyBindingManager.for_prompt( enable_vi_mode=Condition(lambda cli: vi_mode_enabled)) # Add an additional key binding for toggling this flag. @manager.registry.add_binding(Keys.F4) def _(event): " Toggle between Emacs and Vi mode. " nonlocal vi_mode_enabled vi_mode_enabled = not vi_mode_enabled def get_bottom_toolbar_tokens(cli): " Display the current input mode. " text = 'Vi' if vi_mode_enabled else 'Emacs' return [ (Token.Toolbar, ' [F4] %s ' % text) ] prompt('> ', key_bindings_registry=manager.registry, get_bottom_toolbar_tokens=get_bottom_toolbar_tokens) run() prompt_toolkit-0.57/examples/get-password.py0000755000175000017500000000033012604313374022772 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-0.57/examples/asyncio-prompt-simplified.py0000755000175000017500000000337512607101710025466 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ (Python >3.3) This is an example of how to embed a CommandLineInterface 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 cli 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 = cli.stdout_proxy() """ from __future__ import unicode_literals from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.shortcuts import prompt_async import asyncio import sys 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(): """ Coroutine that shows the interactive command line. """ # Run echo loop. Read text from stdin, and reply it back. while True: try: result = await prompt_async('Say something inside the event loop: ', patch_stdout=True) print('You said: "%s"\n' % result) except (EOFError, KeyboardInterrupt): loop.stop() print('Qutting event loop. Bye.') return def main(): asyncio.async(print_counter()) asyncio.async(interactive_shell()) loop.run_forever() loop.close() if __name__ == '__main__': main() prompt_toolkit-0.57/examples/colored-prompt.py0000755000175000017500000000167112620262544023332 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Example of a colored prompt. """ from __future__ import unicode_literals from prompt_toolkit import prompt from prompt_toolkit.styles import PygmentsStyle from pygments.token import Token example_style = PygmentsStyle.from_defaults(style_dict={ # User input. Token: '#ff0066', # Prompt. Token.Username: '#884444 italic', Token.At: '#00aa00', Token.Colon: '#00aa00', Token.Pound: '#00aa00', Token.Host: '#000088 bg:#aaaaff', Token.Path: '#884444 underline', }) def get_prompt_tokens(cli): return [ (Token.Username, 'john'), (Token.At, '@'), (Token.Host, 'localhost'), (Token.Colon, ':'), (Token.Path, '/user/john'), (Token.Pound, '# '), ] if __name__ == '__main__': answer = prompt(get_prompt_tokens=get_prompt_tokens, style=example_style) print('You said: %s' % answer) prompt_toolkit-0.57/examples/no-wrapping.py0000755000175000017500000000035612604313374022624 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-0.57/examples/asyncio-prompt.py0000755000175000017500000000452112620262544023345 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ (Python >= 3.5) This is an example of how to embed a CommandLineInterface 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 cli 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 = cli.stdout_proxy() """ from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.shortcuts import create_prompt_application, create_asyncio_eventloop import asyncio import sys 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(): """ Coroutine that shows the interactive command line. """ # Create an asyncio `EventLoop` object. This is a wrapper around the # asyncio loop that can be passed into prompt_toolkit. eventloop = create_asyncio_eventloop() # Create interface. cli = CommandLineInterface( application=create_prompt_application('Say something inside the event loop: '), eventloop=eventloop) # Patch stdout in something that will always print *above* the prompt when # something is written to stdout. sys.stdout = cli.stdout_proxy() # Run echo loop. Read text from stdin, and reply it back. while True: try: result = await cli.run_async() print('You said: "{0}"'.format(result.text)) except (EOFError, KeyboardInterrupt): return def main(): shell_task = loop.create_task(interactive_shell()) # Gather all the async calls, so they can be cancelled at once 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('Qutting event loop. Bye.') loop.close() if __name__ == '__main__': main() prompt_toolkit-0.57/examples/system-clipboard-integration.py0000755000175000017500000000121312604313374026156 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-0.57/examples/mouse-support.py0000755000175000017500000000062712604313374023226 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-0.57/examples/tutorial/0000755000175000017500000000000012642647210021646 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/examples/tutorial/sqlite-cli.py0000755000175000017500000000323012604313374024266 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals import sys import sqlite3 from prompt_toolkit import AbortAction, prompt from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit.history import InMemoryHistory from pygments.lexers import SqlLexer from pygments.style import Style from pygments.styles.default import DefaultStyle from pygments.token import Token sql_completer = WordCompleter(['create', 'select', 'insert', 'drop', 'delete', 'from', 'where', 'table'], ignore_case=True) class DocumentStyle(Style): styles = { Token.Menu.Completions.Completion.Current: 'bg:#00aaaa #000000', Token.Menu.Completions.Completion: 'bg:#008888 #ffffff', Token.Menu.Completions.ProgressButton: 'bg:#003333', Token.Menu.Completions.ProgressBar: 'bg:#00aaaa', } styles.update(DefaultStyle.styles) def main(database): history = InMemoryHistory() connection = sqlite3.connect(database) while True: try: text = prompt('> ', lexer=SqlLexer, completer=sql_completer, style=DocumentStyle, history=history, on_abort=AbortAction.RETRY) 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-0.57/examples/system-prompt.py0000755000175000017500000000051312621676415023230 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from prompt_toolkit import prompt if __name__ == '__main__': print('If you press meta-! or esc-! at the following prompt, you can enter system commands.') answer = prompt('Give me some input: ', enable_system_bindings=True) print('You said: %s' % answer) prompt_toolkit-0.57/examples/auto-suggestion.py0000755000175000017500000000255212604631173023520 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 unicode_literals, print_function from prompt_toolkit import prompt from prompt_toolkit.history import InMemoryHistory from prompt_toolkit.interface import AbortAction from prompt_toolkit.auto_suggest import AutoSuggestFromHistory def main(): # Create some history first. (Easy for testing.) history = InMemoryHistory() history.append('import os') history.append('print("hello")') history.append('print("world")') history.append('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() text = prompt('Say something: ', history=history, auto_suggest=AutoSuggestFromHistory(), enable_history_search=True, on_abort=AbortAction.RETRY) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/abortaction.retry.py0000755000175000017500000000061312604630260024024 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ Demontration of the RETRY option. Pressing Control-C will not throw a `KeyboardInterrupt` like usual, but instead the prompt is drawn again. """ from __future__ import unicode_literals from prompt_toolkit import prompt, AbortAction if __name__ == '__main__': answer = prompt('Give me some input: ', on_abort=AbortAction.RETRY) print('You said: %s' % answer) prompt_toolkit-0.57/examples/autocorrection.py0000755000175000017500000000241112604313374023415 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.key_binding.manager import KeyBindingManager from prompt_toolkit import prompt # Database of words to be replaced by typing. corrections = { 'impotr': 'import', 'wolrd': 'world', } def main(): # We start with a `KeyBindingManager` instance, because this will already # nicely load all the default key bindings. key_bindings_manager = KeyBindingManager() # We add a custom key binding to space. @key_bindings_manager.registry.add_binding(' ') def _(event): """ When space is pressed, we check the word before the cursor, and autocorrect that. """ b = event.cli.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_registry=key_bindings_manager.registry) print('You said: %s' % text) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/multiline-prompt.py0000755000175000017500000000043012604313374023675 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:\n > ', multiline=True) print('You said: %s' % answer) prompt_toolkit-0.57/examples/clock-input.py0000755000175000017500000000353612620262265022616 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 from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.application import Application from prompt_toolkit.layout import Window from prompt_toolkit.layout.controls import BufferControl from prompt_toolkit.layout.processors import BeforeInput from prompt_toolkit.shortcuts import create_eventloop from prompt_toolkit.utils import Callback from pygments.token import Token import datetime import time def _clock_tokens(cli): " Tokens to be shown before the prompt. " now = datetime.datetime.now() return [ (Token.Prompt, '%s:%s:%s' % (now.hour, now.minute, now.second)), (Token.Prompt, ' Enter something: ') ] def main(): eventloop = create_eventloop() done = [False] # Non local def on_read_start(cli): """ This function is called when we start reading at the input. (Actually the start of the read-input event loop.) """ # Following function should be run in the background. # We do it by using an executor thread from the `CommandLineInterface` # instance. def run(): # Send every second a redraw request. while not done[0]: time.sleep(1) cli.request_redraw() cli.eventloop.run_in_executor(run) def on_read_end(cli): done[0] = True app = Application( layout=Window(BufferControl(input_processors=[BeforeInput(_clock_tokens)])), on_start=Callback(on_read_start), on_stop=Callback(on_read_end)) cli = CommandLineInterface(application=app, eventloop=eventloop) code_obj = cli.run() print('You said: %s' % code_obj.text) eventloop.close() if __name__ == '__main__': main() prompt_toolkit-0.57/examples/regular-language.py0000755000175000017500000000606712620262265023612 0ustar jonathanjonathan00000000000000#!/usr/bin/env python """ This is an example of "prompt_toolkit.contrib.regular_languages" which implements a litle 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 from prompt_toolkit.contrib.completers import WordCompleter from prompt_toolkit import prompt 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.layout.lexers import SimpleLexer from prompt_toolkit.styles import PygmentsStyle from pygments.token import Token import math operators1 = ['add', 'sub', 'div', 'mul'] operators2 = ['sqrt', 'log', 'sin', 'ln'] def create_grammar(): return compile(""" (\s* (?P[a-z]+) \s+ (?P[0-9.]+) \s+ (?P[0-9.]+) \s*) | (\s* (?P[a-z]+) \s+ (?P[0-9.]+) \s*) """) example_style = PygmentsStyle.from_defaults({ Token.Operator: '#33aa33 bold', Token.Number: '#aa3333 bold', Token.TrailingInput: 'bg:#662222 #ffffff', }) if __name__ == '__main__': g = create_grammar() lexer = GrammarLexer(g, lexers={ 'operator1': SimpleLexer(Token.Operator), 'operator2': SimpleLexer(Token.Operator), 'var1': SimpleLexer(Token.Number), 'var2': SimpleLexer(Token.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-0.57/examples/get-password-with-toggle-display-shortcut.py0000755000175000017500000000172412604313374030546 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.key_binding.manager import KeyBindingManager from prompt_toolkit.keys import Keys from prompt_toolkit.filters import Condition def main(): hidden = [True] # Nonlocal key_bindings_manager = KeyBindingManager() @key_bindings_manager.registry.add_binding(Keys.ControlT) 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 cli: hidden[0]), key_bindings_registry=key_bindings_manager.registry) print('You said: %s' % password) if __name__ == '__main__': main() prompt_toolkit-0.57/examples/full-screen-layout.py0000755000175000017500000002020712642105132024103 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.enums import DEFAULT_BUFFER from prompt_toolkit.interface import CommandLineInterface from prompt_toolkit.key_binding.manager import KeyBindingManager from prompt_toolkit.keys import Keys from prompt_toolkit.layout.containers import VSplit, HSplit, Window from prompt_toolkit.layout.controls import BufferControl, FillControl, TokenListControl from prompt_toolkit.layout.dimension import LayoutDimension as D from prompt_toolkit.shortcuts import create_eventloop from pygments.token import Token # 1. First we create the layout # -------------------------- # There are two types of classes that have to be combined to contruct a layout. # We have containers and user controls. Simply said, containers are used for # arranging the layout, we have for instance `HSplit` and `VSplit`. And on the # other hand user controls paint the actual content. We have for instance # `BufferControl` and `TokenListControl`. An important interal difference is that # containers use absolute coordinates, while user controls paint on their own # `Screen` with a relative coordinates. # The Window class itself is a container that can contain a user control, so # that's the adaptor between the two. The Window class also takes care of # scrolling the content if the user control is painting on a screen that is # larger than what was available to the window. # So, for this example, we create a layout that shows the content of the # default buffer on the left, shows a line in the middle and another buffer # (called 'RESULT') on the right. layout = VSplit([ # One window that holds the BufferControl with the default buffer on the # left. Window(content=BufferControl(buffer_name=DEFAULT_BUFFER)), # A vertical line in the middle. We explicitely specify the width, to make # sure that the layout engine will not try to divide the whole width by # three for all these windows. The `FillControl` will simply fill the whole # window by repeating this character. Window(width=D.exact(1), content=FillControl('|', token=Token.Line)), # Display the Result buffer on the right. Window(content=BufferControl(buffer_name='RESULT')), ]) # 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_tokens(cli): return [ (Token.Title, ' Hello world '), (Token.Title, ' (Press [Ctrl-Q] to quit.)'), ] layout = HSplit([ # The titlebar. Window(height=D.exact(1), content=TokenListControl(get_titlebar_tokens, align_center=True)), # Horizontal separator. Window(height=D.exact(1), content=FillControl('-', token=Token.Line)), # The 'body', like defined above. layout, ]) # 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. However instead of # starting with an empty `Registry` instance, usually you'd use a # `KeyBindingmanager`, because it prefills the registry with all of the key # bindings that are required for basic text editing. manager = KeyBindingManager() # Start with the `KeyBindingManager`. # 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 habbit 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. @manager.registry.add_binding(Keys.ControlQ, eager=True) def _(event): """ Pressing Ctrl-Q 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 `CommandLineInterface.run()` call. """ event.cli.set_return_value(None) # 3. Create the buffers # ------------------ # Buffers are the objects that keep track of the user input. In our example, we # have two buffer instances, both are multiline. buffers={ DEFAULT_BUFFER: Buffer(is_multiline=True), 'RESULT': Buffer(is_multiline=True), } # 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 (DEFAULT_BUFFER) changes, update the buffer on the right. We just reverse the text. """ buffers['RESULT'].text = buffers[DEFAULT_BUFFER].text[::-1] buffers[DEFAULT_BUFFER].on_text_changed += default_buffer_changed # 3. Creating an `Application` instance # ---------------------------------- # This glues everything together. application = Application( layout=layout, buffers=buffers, key_bindings_registry=manager.registry, # 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. use_alternate_screen=True) # 4. Run the application # ------------------- def run(): # We need to create an eventloop for this application. An eventloop is # basically a while-true loop that waits for user input, and when it # receives something (like a key press), it will send that to the # application. Usually, you want to use this `create_eventloop` shortcut, # which -- according to the environment (Windows/posix) -- returns # something that will work there. If you want to run your application # inside an "asyncio" environment, you'd have to pass another eventloop. eventloop = create_eventloop() try: # Create a `CommandLineInterface` instance. This is a wrapper around # `Application`, but includes all I/O: eventloops, terminal input and output. cli = CommandLineInterface(application=application, eventloop=eventloop) # Run the interface. (This runs the event loop until Ctrl-Q is pressed.) cli.run() finally: # Clean up. An eventloop creates a posix pipe. This is used internally # for scheduling callables, created in other threads into the main # eventloop. Calling `close` will close this pipe. eventloop.close() if __name__ == '__main__': run() # Some possible improvements. # a) Probably you want to add syntax highlighting to one of these buffers. This # is possible by passing a lexer to the BufferControl. E.g.: # from pygments.lexers import HtmlLexer # from prompt_toolkit.layout.lexers import PygmentsLexer # BufferControl(lexer=PygmentsLexer(HtmlLexer)) # b) Add search functionality. # c) Add additional key bindings to move the focus between the buffers. # d) Add autocompletion. prompt_toolkit-0.57/examples/get-input-vi-mode.py0000755000175000017500000000047412604630260023632 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-0.57/LICENSE0000644000175000000000000000272512556317626016355 0ustar jonathanroot00000000000000Copyright (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-0.57/tests/0000755000175000017500000000000012642647210017327 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/layout_tests/0000755000175000017500000000000012642647210022066 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/layout_tests/__init__.py0000644000175000017500000000137312631120212024165 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.layout.utils import split_lines from pygments.token import Token import unittest class SplitLinesTest(unittest.TestCase): def test_split_lines(self): lines = list(split_lines([(Token.A, 'line1\nline2\nline3')])) self.assertEqual(lines, [ [(Token.A, 'line1')], [(Token.A, 'line2')], [(Token.A, 'line3')], ]) def test_split_lines_2(self): lines = list(split_lines([ (Token.A, 'line1'), (Token.B, 'line2\nline3\nline4') ])) self.assertEqual(lines, [ [(Token.A, 'line1'), (Token.B, 'line2')], [(Token.B, 'line3')], [(Token.B, 'line4')], ]) prompt_toolkit-0.57/tests/screen_tests/0000755000175000017500000000000012642647210022030 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/screen_tests/__init__.py0000644000175000000000000001011412556317626023313 0ustar jonathanroot00000000000000from __future__ import unicode_literals from prompt_toolkit.layout.screen import Screen, Char, Size, Point from pygments.token import Token import unittest class ScreenTest(unittest.TestCase): def setUp(self): self.screen = Screen(Size(rows=10, columns=80)) # def test_current_height(self): # # Screen is still empty, so it doesn't have a height. # self.assertEqual(self.screen.current_height, 1) # # # After writing a character. # self.screen._buffer[5][0] = Char() # self.assertEqual(self.screen.current_height, 6) # def test_get_cursor_position(self): # # Test initial position # self.assertEqual(self.screen.cursor_position, Point(y=0, x=0)) # # # Test after writing something. # self.screen.write_char('w', Token) # self.screen.write_char('x', Token) # self.screen.write_char('y', Token, set_cursor_position=True) # self.screen.write_char('z', Token) # self.assertEqual(self.screen.cursor_position, Point(y=0, x=2)) # # def test_write_char(self): # self.screen.write_char('x', Token.X) # self.screen.write_char('y', Token.Y) # self.screen.write_char('z', Token.Z) # self.screen.write_char('\n', Token) # self.screen.write_char('a', Token.A) # self.screen.write_char('b', Token.B) # self.screen.write_char('c', Token.C) # # self.assertEqual(self.screen._buffer[0][0].char, 'x') # self.assertEqual(self.screen._buffer[0][1].char, 'y') # self.assertEqual(self.screen._buffer[0][2].char, 'z') # self.assertEqual(self.screen._buffer[1][0].char, 'a') # self.assertEqual(self.screen._buffer[1][1].char, 'b') # self.assertEqual(self.screen._buffer[1][2].char, 'c') # # self.assertEqual(self.screen._buffer[0][0].token, Token.X) # self.assertEqual(self.screen._buffer[0][1].token, Token.Y) # self.assertEqual(self.screen._buffer[0][2].token, Token.Z) # self.assertEqual(self.screen._buffer[1][0].token, Token.A) # self.assertEqual(self.screen._buffer[1][1].token, Token.B) # self.assertEqual(self.screen._buffer[1][2].token, Token.C) # # def test_write_at_pos(self): # # Test first write # x = Char('x', Token.X, z_index=0) # self.screen.write_at_pos(5, 3, x) # self.assertEqual(self.screen._buffer[5][3], x) # # # Test higher z_index. # y = Char('y', Token.Y, z_index=10) # self.screen.write_at_pos(5, 3, y) # self.assertEqual(self.screen._buffer[5][3], y) # # # Test lower z_index. (Should not replace.) # z = Char('z', Token.Z, z_index=8) # self.screen.write_at_pos(5, 3, z) # self.assertEqual(self.screen._buffer[5][3], y) #def test_write_highlighted_at_pos(self): # self.screen.write_highlighted_at_pos(4, 2, [(Token.ABC, 'abc'), (Token.DEF, 'def')]) # self.assertEqual(self.screen._buffer[4][2].char, 'a') # self.assertEqual(self.screen._buffer[4][3].char, 'b') # self.assertEqual(self.screen._buffer[4][4].char, 'c') # self.assertEqual(self.screen._buffer[4][2].token, Token.ABC) # self.assertEqual(self.screen._buffer[4][3].token, Token.ABC) # self.assertEqual(self.screen._buffer[4][4].token, Token.ABC) # self.assertEqual(self.screen._buffer[4][5].char, 'd') # self.assertEqual(self.screen._buffer[4][6].char, 'e') # self.assertEqual(self.screen._buffer[4][7].char, 'f') # self.assertEqual(self.screen._buffer[4][5].token, Token.DEF) # self.assertEqual(self.screen._buffer[4][6].token, Token.DEF) # self.assertEqual(self.screen._buffer[4][7].token, Token.DEF) #def test_write_highlighted(self): # self.screen.write_highlighted([(Token.ABC, 'abc'), (Token.DEF, 'def')]) # self.screen.write_highlighted([(Token.GHI, 'ghi'), (Token.JKL, 'jkl')]) # self.assertEqual(self.screen._buffer[0][4].char, 'e') # self.assertEqual(self.screen._buffer[0][8].char, 'i') # self.assertEqual(self.screen._buffer[0][4].token, Token.DEF) # self.assertEqual(self.screen._buffer[0][8].token, Token.GHI) prompt_toolkit-0.57/tests/key_binding_tests/0000755000175000017500000000000012642647210023033 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/key_binding_tests/__init__.py0000644000175000000000000000546212556317626024330 0ustar jonathanroot00000000000000from __future__ import unicode_literals from prompt_toolkit.key_binding.input_processor import InputProcessor, KeyPress from prompt_toolkit.key_binding.registry import Registry from prompt_toolkit.keys import Keys import unittest class KeyBindingTest(unittest.TestCase): def setUp(self): class Handlers(object): def __init__(self): self.called = [] def __getattr__(self, name): def func(event): self.called.append(name) return func self.handlers = Handlers() self.registry = Registry() self.registry.add_binding(Keys.ControlX, Keys.ControlC)(self.handlers.controlx_controlc) self.registry.add_binding(Keys.ControlX)(self.handlers.control_x) self.registry.add_binding(Keys.ControlD)(self.handlers.control_d) self.registry.add_binding(Keys.ControlSquareClose, Keys.Any)(self.handlers.control_square_close_any) self.processor = InputProcessor(self.registry, lambda: None) def test_feed_simple(self): self.processor.feed_key(KeyPress(Keys.ControlX, '\x18')) self.processor.feed_key(KeyPress(Keys.ControlC, '\x03')) self.assertEqual(self.handlers.called, ['controlx_controlc']) def test_feed_several(self): # First an unknown key first. self.processor.feed_key(KeyPress(Keys.ControlQ, '')) self.assertEqual(self.handlers.called, []) # Followed by a know key sequence. self.processor.feed_key(KeyPress(Keys.ControlX, '')) self.processor.feed_key(KeyPress(Keys.ControlC, '')) self.assertEqual(self.handlers.called, ['controlx_controlc']) # Followed by another unknown sequence. self.processor.feed_key(KeyPress(Keys.ControlR, '')) self.processor.feed_key(KeyPress(Keys.ControlS, '')) # Followed again by a know key sequence. self.processor.feed_key(KeyPress(Keys.ControlD, '')) self.assertEqual(self.handlers.called, ['controlx_controlc', 'control_d']) def test_control_square_closed_any(self): self.processor.feed_key(KeyPress(Keys.ControlSquareClose, '')) self.processor.feed_key(KeyPress('C', 'C')) self.assertEqual(self.handlers.called, ['control_square_close_any']) def test_common_prefix(self): # Sending Control_X should not yet do anything, because there is # another sequence starting with that as well. self.processor.feed_key(KeyPress(Keys.ControlX, '')) self.assertEqual(self.handlers.called, []) # When another key is pressed, we know that we did not meant the longer # "ControlX ControlC" sequence and the callbacks are called. self.processor.feed_key(KeyPress(Keys.ControlD, '')) self.assertEqual(self.handlers.called, ['control_x', 'control_d']) prompt_toolkit-0.57/tests/buffer_tests/0000755000175000017500000000000012642647210022022 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/buffer_tests/__init__.py0000644000175000000000000000634112556317626023314 0ustar jonathanroot00000000000000from __future__ import unicode_literals from prompt_toolkit.buffer import Buffer import unittest class BufferTest(unittest.TestCase): def setUp(self): self.buffer = Buffer() def test_initial(self): self.assertEqual(self.buffer.text, '') self.assertEqual(self.buffer.cursor_position, 0) def test_insert_text(self): self.buffer.insert_text('some_text') self.assertEqual(self.buffer.text, 'some_text') self.assertEqual(self.buffer.cursor_position, len('some_text')) def test_cursor_movement(self): self.buffer.insert_text('some_text') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.cursor_right() self.buffer.insert_text('A') self.assertEqual(self.buffer.text, 'some_teAxt') self.assertEqual(self.buffer.cursor_position, len('some_teA')) def test_backspace(self): self.buffer.insert_text('some_text') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.delete_before_cursor() self.assertEqual(self.buffer.text, 'some_txt') self.assertEqual(self.buffer.cursor_position, len('some_t')) def test_cursor_up(self): # Cursor up to a line thats longer. self.buffer.insert_text('long line1\nline2') self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) # Going up when already at the top. self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) # Going up to a line that's shorter. self.buffer.reset() self.buffer.insert_text('line1\nlong line2') self.buffer.cursor_up() self.assertEqual(self.buffer.document.cursor_position, 5) def test_cursor_down(self): self.buffer.insert_text('line1\nline2') self.buffer.cursor_position = 3 # Normally going down self.buffer.cursor_down() self.assertEqual(self.buffer.document.cursor_position, len('line1\nlin')) # Going down to a line that's storter. self.buffer.reset() self.buffer.insert_text('long line1\na\nb') self.buffer.cursor_position = 3 self.buffer.cursor_down() self.assertEqual(self.buffer.document.cursor_position, len('long line1\na')) def test_join_next_line(self): self.buffer.insert_text('line1\nline2\nline3') self.buffer.cursor_up() self.buffer.join_next_line() self.assertEqual(self.buffer.text, 'line1\nline2 line3') # Test when there is no '\n' in the text self.buffer.reset() self.buffer.insert_text('line1') self.buffer.cursor_position = 0 self.buffer.join_next_line() self.assertEqual(self.buffer.text, 'line1') def test_newline(self): self.buffer.insert_text('hello world') self.buffer.newline() self.assertEqual(self.buffer.text, 'hello world\n') def test_swap_characters_before_cursor(self): self.buffer.insert_text('hello world') self.buffer.cursor_left() self.buffer.cursor_left() self.buffer.swap_characters_before_cursor() self.assertEqual(self.buffer.text, 'hello wrold') prompt_toolkit-0.57/tests/regular_languages_tests/0000755000175000017500000000000012642647210024240 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/regular_languages_tests/__init__.py0000644000175000000000000000726012556317626025533 0ustar jonathanroot00000000000000from __future__ import unicode_literals 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.completion import Completer, Completion, CompleteEvent from prompt_toolkit.document import Document import unittest class GrammarTest(unittest.TestCase): def test_simple_match(self): g = compile('hello|world') m = g.match('hello') self.assertTrue(isinstance(m, Match)) m = g.match('world') self.assertTrue(isinstance(m, Match)) m = g.match('somethingelse') self.assertEqual(m, None) def test_variable_varname(self): """ Test `Variable` with varname. """ g = compile('((?Phello|world)|test)') m = g.match('hello') variables = m.variables() self.assertTrue(isinstance(variables, Variables)) self.assertEqual(variables.get('varname'), 'hello') self.assertEqual(variables['varname'], 'hello') m = g.match('world') variables = m.variables() self.assertTrue(isinstance(variables, Variables)) self.assertEqual(variables.get('varname'), 'world') self.assertEqual(variables['varname'], 'world') m = g.match('test') variables = m.variables() self.assertTrue(isinstance(variables, Variables)) self.assertEqual(variables.get('varname'), None) self.assertEqual(variables['varname'], None) def test_prefix(self): """ Test `match_prefix`. """ g = compile(r'(hello\ world|something\ else)') m = g.match_prefix('hello world') self.assertTrue(isinstance(m, Match)) m = g.match_prefix('he') self.assertTrue(isinstance(m, Match)) m = g.match_prefix('') self.assertTrue(isinstance(m, Match)) m = g.match_prefix('som') self.assertTrue(isinstance(m, Match)) m = g.match_prefix('hello wor') self.assertTrue(isinstance(m, Match)) m = g.match_prefix('no-match') self.assertEqual(m.trailing_input().start, 0) self.assertEqual(m.trailing_input().stop, len('no-match')) m = g.match_prefix('hellotest') self.assertEqual(m.trailing_input().start, len('hello')) self.assertEqual(m.trailing_input().stop, len('hellotest')) def test_completer(self): 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())) self.assertEqual(len(completions), 2) self.assertEqual(completions[0].text, 'before2-def-after2') self.assertEqual(completions[0].start_position, -3) self.assertEqual(completions[1].text, 'before2-def-after2-B') self.assertEqual(completions[1].start_position, -3) prompt_toolkit-0.57/tests/inputstream_tests/0000755000175000017500000000000012642647210023124 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/inputstream_tests/__init__.py0000644000175000000000000001202612556317626024413 0ustar jonathanroot00000000000000from __future__ import unicode_literals from prompt_toolkit.terminal.vt100_input import InputStream from prompt_toolkit.keys import Keys import unittest class InputStreamTest(unittest.TestCase): def setUp(self): class _ProcessorMock(object): def __init__(self): self.keys = [] def feed_key(self, key_press): self.keys.append(key_press) self.processor = _ProcessorMock() self.stream = InputStream(self.processor.feed_key) def test_control_keys(self): self.stream.feed('\x01\x02\x10') self.assertEqual(len(self.processor.keys), 3) self.assertEqual(self.processor.keys[0].key, Keys.ControlA) self.assertEqual(self.processor.keys[1].key, Keys.ControlB) self.assertEqual(self.processor.keys[2].key, Keys.ControlP) self.assertEqual(self.processor.keys[0].data, '\x01') self.assertEqual(self.processor.keys[1].data, '\x02') self.assertEqual(self.processor.keys[2].data, '\x10') def test_arrows(self): self.stream.feed('\x1b[A\x1b[B\x1b[C\x1b[D') self.assertEqual(len(self.processor.keys), 4) self.assertEqual(self.processor.keys[0].key, Keys.Up) self.assertEqual(self.processor.keys[1].key, Keys.Down) self.assertEqual(self.processor.keys[2].key, Keys.Right) self.assertEqual(self.processor.keys[3].key, Keys.Left) self.assertEqual(self.processor.keys[0].data, '\x1b[A') self.assertEqual(self.processor.keys[1].data, '\x1b[B') self.assertEqual(self.processor.keys[2].data, '\x1b[C') self.assertEqual(self.processor.keys[3].data, '\x1b[D') def test_escape(self): self.stream.feed('\x1bhello') self.assertEqual(len(self.processor.keys), 1 + len('hello')) self.assertEqual(self.processor.keys[0].key, Keys.Escape) self.assertEqual(self.processor.keys[1].key, 'h') self.assertEqual(self.processor.keys[0].data, '\x1b') self.assertEqual(self.processor.keys[1].data, 'h') def test_special_double_keys(self): self.stream.feed('\x1b[1;3D') # Should both send escape and left. self.assertEqual(len(self.processor.keys), 2) self.assertEqual(self.processor.keys[0].key, Keys.Escape) self.assertEqual(self.processor.keys[1].key, Keys.Left) self.assertEqual(self.processor.keys[0].data, '\x1b[1;3D') self.assertEqual(self.processor.keys[1].data, '\x1b[1;3D') def test_flush_1(self): # Send left key in two parts without flush. self.stream.feed('\x1b') self.stream.feed('[D') self.assertEqual(len(self.processor.keys), 1) self.assertEqual(self.processor.keys[0].key, Keys.Left) self.assertEqual(self.processor.keys[0].data, '\x1b[D') def test_flush_2(self): # Send left key with a 'Flush' in between. # The flush should make sure that we process evenything before as-is, # with makes the first part just an escape character instead. self.stream.feed('\x1b') self.stream.flush() self.stream.feed('[D') self.assertEqual(len(self.processor.keys), 3) self.assertEqual(self.processor.keys[0].key, Keys.Escape) self.assertEqual(self.processor.keys[1].key, '[') self.assertEqual(self.processor.keys[2].key, 'D') self.assertEqual(self.processor.keys[0].data, '\x1b') self.assertEqual(self.processor.keys[1].data, '[') self.assertEqual(self.processor.keys[2].data, 'D') def test_meta_arrows(self): self.stream.feed('\x1b\x1b[D') self.assertEqual(len(self.processor.keys), 2) self.assertEqual(self.processor.keys[0].key, Keys.Escape) self.assertEqual(self.processor.keys[1].key, Keys.Left) def test_control_square_close(self): self.stream.feed('\x1dC') self.assertEqual(len(self.processor.keys), 2) self.assertEqual(self.processor.keys[0].key, Keys.ControlSquareClose) self.assertEqual(self.processor.keys[1].key, 'C') def test_invalid(self): # Invalid sequence that has at two characters in common with other # sequences. self.stream.feed('\x1b[*') self.assertEqual(len(self.processor.keys), 3) self.assertEqual(self.processor.keys[0].key, Keys.Escape) self.assertEqual(self.processor.keys[1].key, '[') self.assertEqual(self.processor.keys[2].key, '*') def test_cpr_response(self): self.stream.feed('a\x1b[40;10Rb') self.assertEqual(len(self.processor.keys), 3) self.assertEqual(self.processor.keys[0].key, 'a') self.assertEqual(self.processor.keys[1].key, Keys.CPRResponse) self.assertEqual(self.processor.keys[2].key, 'b') def test_cpr_response_2(self): # Make sure that the newline is not included in the CPR response. self.stream.feed('\x1b[40;1R\n') self.assertEqual(len(self.processor.keys), 2) self.assertEqual(self.processor.keys[0].key, Keys.CPRResponse) self.assertEqual(self.processor.keys[1].key, Keys.ControlJ) prompt_toolkit-0.57/tests/run_tests.py0000755000175000017500000000340712642112523021730 0ustar jonathanjonathan00000000000000#!/usr/bin/env python from __future__ import unicode_literals from buffer_tests import * from document_tests import * from inputstream_tests import * from key_binding_tests import * from screen_tests import * from regular_languages_tests import * from layout_tests import * from contrib_tests import * from utils_tests import * import unittest # Import modules for syntax checking. import prompt_toolkit import prompt_toolkit.application import prompt_toolkit.auto_suggest import prompt_toolkit.buffer import prompt_toolkit.buffer_mapping import prompt_toolkit.clipboard import prompt_toolkit.completion import prompt_toolkit.contrib.completers import prompt_toolkit.contrib.regular_languages import prompt_toolkit.contrib.telnet import prompt_toolkit.contrib.validators import prompt_toolkit.document import prompt_toolkit.enums import prompt_toolkit.eventloop.base import prompt_toolkit.eventloop.inputhook import prompt_toolkit.eventloop.posix import prompt_toolkit.eventloop.posix_utils import prompt_toolkit.eventloop.utils import prompt_toolkit.filters.base import prompt_toolkit.filters.cli import prompt_toolkit.filters.types import prompt_toolkit.filters.utils import prompt_toolkit.history import prompt_toolkit.input import prompt_toolkit.interface import prompt_toolkit.key_binding import prompt_toolkit.keys import prompt_toolkit.layout import prompt_toolkit.output import prompt_toolkit.reactive import prompt_toolkit.renderer import prompt_toolkit.search_state import prompt_toolkit.selection import prompt_toolkit.shortcuts import prompt_toolkit.styles import prompt_toolkit.terminal import prompt_toolkit.terminal.vt100_input import prompt_toolkit.terminal.vt100_output import prompt_toolkit.utils import prompt_toolkit.validation if __name__ == '__main__': unittest.main() prompt_toolkit-0.57/tests/document_tests/0000755000175000017500000000000012642647210022367 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/document_tests/__init__.py0000644000175000017500000000373512640634725024515 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.document import Document import unittest class DocumentTest(unittest.TestCase): def setUp(self): self.document = Document( 'line 1\n' + 'line 2\n' + 'line 3\n' + 'line 4\n', len('line 1\n' + 'lin') ) def test_current_char(self): self.assertEqual(self.document.current_char, 'e') def test_text_before_cursor(self): self.assertEqual(self.document.text_before_cursor, 'line 1\nlin') def test_text_after_cursor(self): self.assertEqual(self.document.text_after_cursor, 'e 2\n' + 'line 3\n' + 'line 4\n') def test_lines(self): self.assertEqual(self.document.lines, [ 'line 1', 'line 2', 'line 3', 'line 4', '']) def test_line_count(self): self.assertEqual(self.document.line_count, 5) def test_current_line_before_cursor(self): self.assertEqual(self.document.current_line_before_cursor, 'lin') def test_current_line_after_cursor(self): self.assertEqual(self.document.current_line_after_cursor, 'e 2') def test_current_line(self): self.assertEqual(self.document.current_line, 'line 2') def test_cursor_position(self): self.assertEqual(self.document.cursor_position_row, 1) self.assertEqual(self.document.cursor_position_col, 3) d = Document('', 0) self.assertEqual(d.cursor_position_row, 0) self.assertEqual(d.cursor_position_col, 0) def test_translate_index_to_position(self): pos = self.document.translate_index_to_position( len('line 1\nline 2\nlin')) self.assertEqual(pos[0], 2) self.assertEqual(pos[1], 3) pos = self.document.translate_index_to_position(0) self.assertEqual(pos, (0, 0)) prompt_toolkit-0.57/tests/contrib_tests.py0000644000175000017500000002106512605350532022564 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from __future__ import absolute_import from __future__ import print_function import os import shutil import tempfile import unittest from six import text_type from prompt_toolkit.completion import CompleteEvent from prompt_toolkit.document import Document from prompt_toolkit.contrib.completers.filesystem import PathCompleter class chdir(object): """Context manager for current working directory temporary change.""" def __init__(self, directory): self.new_dir = directory self.orig_dir = os.getcwd() def __enter__(self): os.chdir(self.new_dir) def __exit__(self, *args): os.chdir(self.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')) class PathCompleterTest(unittest.TestCase): def test_pathcompleter_completes_in_current_directory(self): completer = PathCompleter() doc_text = '' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) self.assertTrue(len(completions) > 0) def test_pathcompleter_completes_files_in_current_directory(self): # 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) self.assertEqual(expected, result) # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_files_in_absolute_directory(self): # 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]) self.assertEqual(expected, result) # cleanup shutil.rmtree(test_dir) def test_pathcompleter_completes_directories_with_only_directories(self): # 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] self.assertEqual(['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)) self.assertEqual([], completions) # cleanup shutil.rmtree(test_dir) def test_pathcompleter_respects_completions_under_min_input_len(self): # 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)) self.assertEqual([], 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] self.assertEqual([''], 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] self.assertEqual([''], 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) self.assertEqual(['', '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)) self.assertEqual([], completions) # cleanup shutil.rmtree(test_dir) def test_pathcompleter_does_not_expanduser_by_default(self): completer = PathCompleter() doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) self.assertEqual([], completions) def test_pathcompleter_can_expanduser(self): completer = PathCompleter(expanduser=True) doc_text = '~' doc = Document(doc_text, len(doc_text)) event = CompleteEvent() completions = list(completer.get_completions(doc, event)) self.assertTrue(len(completions) > 0) def test_pathcompleter_can_apply_file_filter(self): # 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] self.assertEqual(['my.csv'], result) # cleanup shutil.rmtree(test_dir) def test_pathcompleter_get_paths_constrains_path(self): # 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'] self.assertEqual(expected, result) # cleanup shutil.rmtree(test_dir) prompt_toolkit-0.57/tests/utils_tests/0000755000175000017500000000000012642647210021711 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/tests/utils_tests/__init__.py0000644000175000017500000000274412631120127024020 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.utils import take_using_weights import unittest import itertools class SplitLinesTest(unittest.TestCase): def test_using_weights(self): 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) self.assertEqual(data.count('A'), 5) self.assertEqual(data.count('B'), 10) self.assertEqual(data.count('C'), 20) self.assertEqual(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) self.assertEqual(data.count('A'), 20) self.assertEqual(data.count('B'), 10) self.assertEqual(data.count('C'), 5) # Bigger numbers. data = take(take_using_weights(['A', 'B', 'C'], [20, 10, 5]), 70) self.assertEqual(data.count('A'), 40) self.assertEqual(data.count('B'), 20) self.assertEqual(data.count('C'), 10) # Negative numbers. data = take(take_using_weights(['A', 'B', 'C'], [-20, 10, 0]), 70) self.assertEqual(data.count('A'), 0) self.assertEqual(data.count('B'), 70) self.assertEqual(data.count('C'), 0) prompt_toolkit-0.57/prompt_toolkit/0000755000175000017500000000000012642647210021253 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/document.py0000644000175000017500000006424712640655763023472 0ustar jonathanjonathan00000000000000""" """ from __future__ import unicode_literals import re import six import string from .selection import SelectionType, SelectionState from .clipboard import ClipboardData __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*)') 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', '_lines_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) self.text = text self.cursor_position = cursor_position self.selection = selection self._lines_cache = None def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.text, self.cursor_position) @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. """ return self.text_before_cursor.split('\n')[-1] @property def current_line_after_cursor(self): """ Text from the cursor until the end of the line. """ return self.text_after_cursor.split('\n')[0] @property def lines(self): " Array of all the lines. " # Cache, because this one is reused very often. if self._lines_cache is None: self._lines_cache = self.text.split('\n') return self._lines_cache @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.) """ return len(self.text_before_cursor.split('\n')) - 1 @property def cursor_position_col(self): """ Current column. (0-based.) """ return len(self.current_line_before_cursor) def translate_index_to_position(self, index): # TODO: make this 0-based indexed!!! """ Given an index for the text, return the corresponding (row, col) tuple. (0-based. Returns (0, 0) for index=0.) """ text_before_position = self.text[:index] row = text_before_position.count('\n') col = len(text_before_position.split('\n')[-1]) 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.) """ result = len('\n'.join(self.lines[:row])) + (len('\n') if row > 0 else 0) + col # 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.cursor_position_col == len(self.current_line) def has_match_at_current_position(self, sub): """ `True` when this substring is found at the cursor position. """ return self.text[self.cursor_position:].find(sub) == 0 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 occurance. """ 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 occurances 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 occurance. """ 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): """ Give the word before the cursor. If we have whitespace before the cursor this returns an empty string. """ if self.text_before_cursor[-1:].isspace(): return '' else: return self.text_before_cursor[self.find_start_of_previous_word(WORD=WORD):] def find_start_of_previous_word(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. """ # Reverse the text before the cursor, in order to do an efficient # backwards search. text_before_cursor = 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): if i + 1 == count: return - match.end(1) 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. """ 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 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 next word. Return `None` if nothing was found. """ 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_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. """ return - min(self.cursor_position_col, count) def get_cursor_right_position(self, count=1): """ Relative position for cursor_right. """ return min(count, len(self.current_line_after_cursor)) def get_cursor_up_position(self, count=1): """ Return the relative cursor position (character index) where we would be if the user pressed the arrow-up button. """ assert count >= 1 count = min(self.text_before_cursor.count('\n'), count) if count: pos = self.cursor_position_col lines = self.text_before_cursor.split('\n') skip_lines = '\n'.join(lines[-count-1:]) new_line = lines[-count-1] # When the current line is longer then the previous, move to the # last character of the previous line. if pos > len(new_line): return - len(skip_lines) + len(new_line) # Otherwise find the corresponding position in the previous line. else: return - len(skip_lines) + pos return 0 def get_cursor_down_position(self, count=1): """ Return the relative cursor position (character index) where we would be if the user pressed the arrow-down button. """ assert count >= 1 count = min(self.text_after_cursor.count('\n'), count) if count: pos = self.cursor_position_col lines = self.text_after_cursor.split('\n') skip_lines = '\n'.join(lines[:count]) new_line = lines[count] # When the current line is longer then the previous, move to the # last character of the next line. if pos > len(new_line): return len(skip_lines) + len(new_line) + 1 # Otherwise find the corresponding position in the next line. else: return len(skip_lines) + pos + 1 return 0 @property def matching_bracket_position(self): """ Return relative cursor position of matching [, (, { or < bracket. """ stack = 1 for A, B in '()', '[]', '{}', '<>': if self.current_char == A: for i, c in enumerate(self.text_after_cursor[1:]): if c == A: stack += 1 elif c == B: stack -= 1 if stack == 0: return i + 1 elif self.current_char == B: for i, c in enumerate(reversed(self.text_before_cursor)): if c == B: stack += 1 elif c == A: stack -= 1 if stack == 0: return - (i + 1) 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_after_cursor.rstrip()) 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): """ 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. start and end position are always included in the selection. This will yield several (from, to) tuples in case of 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 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 - 1, 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[:from_].rfind('\n') + 1) if self.text[to:].find('\n') >= 0: to += self.text[to:].find('\n') else: to = len(self.text) yield from_, to 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 + 1]) last_to = to + 1 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, before=False, count=1): """ Return a new :class:`.Document` instance which contains the result if we would paste this data at the current cursor position. :param before: Paste before the cursor position. (For line/character mode.) :param count: When >1, Paste multiple times. """ assert isinstance(data, ClipboardData) if data.type == SelectionType.CHARACTERS: if before: new_text = self.text_before_cursor + data.text * count + self.text_after_cursor new_cursor_position = self.cursor_position + len(data.text) * count - 1 else: new_text = (self.text[:self.cursor_position + 1] + data.text * count + self.text[self.cursor_position + 1:]) new_cursor_position = self.cursor_position + len(data.text) * count 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 # 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-0.57/prompt_toolkit/utils.py0000644000175000017500000001271212631451002022756 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import os import signal import sys import threading from wcwidth import wcwidth __all__ = ( 'Callback', 'DummyContext', 'get_cwidth', 'suspend_to_background_supported', 'is_conemu_ansi', 'is_windows', 'in_main_thread', 'SimpleLRUCache', 'take_using_weights', ) class Callback(object): """ Callbacks wrapper. Used for event propagation. There are two ways of using it. The first way is to create a callback instance from a callable and pass it to the code that's going to fire it. (This can also be used as a decorator.) :: c = Callback(function) c.fire() The second way is that the code who's going to fire the callback, already created an Callback instance. Then handlers can be attached using the ``+=`` operator:: c = Callback() c += handler_function # Add event handler. c.fire() # Fire event. """ def __init__(self, func=None): assert func is None or callable(func) self._handlers = [func] if func else [] def fire(self, *args, **kwargs): """ Trigger callback. """ for handler in self._handlers: handler(*args, **kwargs) def __iadd__(self, handler): """ Add another handler to this callback. """ self._handlers.append(handler) return self def __isub__(self, handler): """ Remove a handler from this callback. """ self._handlers.remove(handler) return self def __or__(self, other): """ Chain two callbacks, using the | operator. """ assert isinstance(other, Callback) def call_both(): self.fire() other.fire() return Callback(call_both) 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. """ 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(max(0, wcwidth(c)) for c in string) self[string] = result 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_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' class SimpleLRUCache(object): """ Very simple LRU cache. :param maxsize: Maximum size of the cache. (Don't make it too big.) """ def __init__(self, maxsize=8): self.maxsize = maxsize self._cache = [] # List of (key, value). 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. for k, v in self._cache: if k == key: return v # Not found? Get it. value = getter_func() self._cache.append((key, value)) if len(self._cache) > self.maxsize: self._cache = self._cache[-self.maxsize:] return value 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 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 prompt_toolkit-0.57/prompt_toolkit/clipboard/0000755000175000017500000000000012642647210023212 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/clipboard/in_memory.py0000644000175000017500000000066112556570471025575 0ustar jonathanjonathan00000000000000from .base import Clipboard, ClipboardData __all__ = ( 'InMemoryClipboard', ) class InMemoryClipboard(Clipboard): """ Default clipboard implementation. Just keep the data in memory. """ def __init__(self): self._data = None def set_data(self, data): assert isinstance(data, ClipboardData) self._data = data def get_data(self): return self._data or ClipboardData() prompt_toolkit-0.57/prompt_toolkit/clipboard/pyperclip.py0000644000175000017500000000216112560300720025562 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-0.57/prompt_toolkit/clipboard/__init__.py0000644000175000017500000000036512556570471025337 0ustar jonathanjonathan00000000000000from .base import Clipboard, ClipboardData 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 prompt_toolkit-0.57/prompt_toolkit/clipboard/base.py0000644000175000017500000000256512640634725024513 0ustar jonathanjonathan00000000000000""" Clipboard for command line interface. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass import six from prompt_toolkit.selection import SelectionType __all__ = ( 'Clipboard', 'ClipboardData', ) 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)) @abstractmethod def get_data(self): """ Return clipboard data. """ prompt_toolkit-0.57/prompt_toolkit/filters/0000755000175000017500000000000012642647210022723 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/filters/utils.py0000644000175000017500000000174212607075671024450 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .base import Always, Never from .types import SimpleFilter, CLIFilter __all__ = ( 'to_cli_filter', 'to_simple_filter', ) _always = Always() _never = Never() def to_simple_filter(bool_or_filter): """ Accept both booleans and CLIFilters as input and turn it into a SimpleFilter. """ assert isinstance(bool_or_filter, (bool, SimpleFilter)), \ TypeError('Expecting a bool or a SimpleFilter instance. Got %r' % bool_or_filter) return { True: _always, False: _never, }.get(bool_or_filter, bool_or_filter) def to_cli_filter(bool_or_filter): """ Accept both booleans and CLIFilters as input and turn it into a CLIFilter. """ assert isinstance(bool_or_filter, (bool, CLIFilter)), \ TypeError('Expecting a bool or a CLIFilter instance. Got %r' % bool_or_filter) return { True: _always, False: _never, }.get(bool_or_filter, bool_or_filter) prompt_toolkit-0.57/prompt_toolkit/filters/cli.py0000644000175000017500000000777212642105755024064 0ustar jonathanjonathan00000000000000""" Filters that accept a `CommandLineInterface` as argument. """ from __future__ import unicode_literals from .base import Filter __all__ = ( 'HasArg', 'HasCompletions', 'HasFocus', 'InFocusStack', 'HasSearch', 'HasSelection', 'HasValidationError', 'IsAborting', 'IsDone', 'IsMultiline', 'IsReadOnly', 'IsReturning', 'RendererHeightIsKnown', ) class HasFocus(Filter): """ Enable when this buffer has the focus. """ def __init__(self, buffer_name): self.buffer_name = buffer_name def __call__(self, cli): return cli.current_buffer_name == self.buffer_name def __repr__(self): return 'HasFocus(%r)' % self.buffer_name class InFocusStack(Filter): """ Enable when this buffer appears on the focus stack. """ def __init__(self, buffer_name): self.buffer_name = buffer_name def __call__(self, cli): return self.buffer_name in cli.buffers.focus_stack def __repr__(self): return 'InFocusStack(%r)' % self.buffer_name class HasSelection(Filter): """ Enable when the current buffer has a selection. """ def __call__(self, cli): return bool(cli.current_buffer.selection_state) def __repr__(self): return 'HasSelection()' class HasCompletions(Filter): """ Enable when the current buffer has completions. """ def __call__(self, cli): return cli.current_buffer.complete_state is not None def __repr__(self): return 'HasCompletions()' class IsMultiline(Filter): """ Enable in multiline mode. """ def __call__(self, cli): return cli.current_buffer.is_multiline() def __repr__(self): return 'IsMultiline()' class IsReadOnly(Filter): """ True when the current buffer is read only. """ def __call__(self, cli): return cli.current_buffer.read_only() def __repr__(self): return 'IsReadOnly()' class HasValidationError(Filter): """ Current buffer has validation error. """ def __call__(self, cli): return cli.current_buffer.validation_error is not None def __repr__(self): return 'HasValidationError()' class HasArg(Filter): """ Enable when the input processor has an 'arg'. """ def __call__(self, cli): return cli.input_processor.arg is not None def __repr__(self): return 'HasArg()' class HasSearch(Filter): """ Incremental search is active. """ def __call__(self, cli): return cli.is_searching def __repr__(self): return 'HasSearch()' class IsReturning(Filter): """ When a return value has been set. """ def __call__(self, cli): return cli.is_returning def __repr__(self): return 'IsReturning()' class IsAborting(Filter): """ True when aborting. (E.g. Control-C pressed.) """ def __call__(self, cli): return cli.is_aborting def __repr__(self): return 'IsAborting()' class IsExiting(Filter): """ True when exiting. (E.g. Control-D pressed.) """ def __call__(self, cli): return cli.is_exiting def __repr__(self): return 'IsExiting()' class IsDone(Filter): """ True when the CLI is returning, aborting or exiting. """ def __call__(self, cli): return cli.is_done def __repr__(self): return 'IsDone()' class RendererHeightIsKnown(Filter): """ 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.) """ def __call__(self, cli): return cli.renderer.height_is_known def __repr__(self): return 'RendererHeightIsKnown()' prompt_toolkit-0.57/prompt_toolkit/filters/types.py0000644000175000000000000000330612607072230023602 0ustar jonathanroot00000000000000from __future__ import unicode_literals from inspect import ArgSpec from six import with_metaclass __all__ = ( 'CLIFilter', 'SimpleFilter', 'check_signatures_are_equal', ) class _FilterTypeMeta(type): def __instancecheck__(cls, instance): if not hasattr(instance, 'getargspec'): return False arguments = _drop_self(instance.getargspec()) return arguments.args == cls.arguments_list or arguments.varargs is not None class _FilterType(with_metaclass(_FilterTypeMeta)): def __new__(cls): raise NotImplementedError('This class should not be initiated.') class CLIFilter(_FilterType): """ Abstract base class for filters that accept a :class:`~prompt_toolkit.interface.CommandLineInterface` argument. It cannot be instantiated, it's only to be used for instance assertions, e.g.:: isinstance(my_filter, CliFilter) """ arguments_list = ['cli'] class SimpleFilter(_FilterType): """ Abstract base class for filters that don't accept any arguments. """ arguments_list = [] def _drop_self(spec): """ Take an argspec and return a new one without the 'self'. """ args, varargs, varkw, defaults = spec if args[0:1] == ['self']: args = args[1:] return ArgSpec(args, varargs, varkw, defaults) def check_signatures_are_equal(lst): """ Check whether all filters in this list have the same signature. Raises `TypeError` if not. """ spec = _drop_self(lst[0].getargspec()) for f in lst[1:]: if _drop_self(f.getargspec()) != spec: raise TypeError('Trying to chain filters with different signature: %r and %r' % (lst[0], f)) prompt_toolkit-0.57/prompt_toolkit/filters/__init__.py0000644000175000017500000000323312556570471025045 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. One class of filters observe a `CommandLineInterface` instance. However, they are not attached to such an instance. (We have to pass this instance to the filter when calling it.) The reason for this is to allow declarative programming: for key bindings, we can attach a filter to a key binding without knowing yet which `CommandLineInterface` instance it will observe in the end. Examples are `HasSearch` or `IsExiting`. Another class of filters doesn't take anything as input. And a third class of filters are universal, for instance `Always` and `Never`. It is impossible to mix the first and the second class, because that would mean mixing filters with a different signature. Filters can be chained using ``&`` and ``|`` operations, and inverted using the ``~`` operator, for instance:: filter = HasFocus('default') & ~ HasSelection() """ from __future__ import unicode_literals from .base import * from .cli import * from .types import * from .utils import * prompt_toolkit-0.57/prompt_toolkit/filters/base.py0000644000175000000000000001426212607067307023364 0ustar jonathanroot00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from .types import check_signatures_are_equal import inspect __all__ = ( 'Filter', 'Never', 'Always', 'Condition', ) class Filter(with_metaclass(ABCMeta, object)): """ 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, *a, **kw): """ The actual call to evaluate the filter. """ return True def __and__(self, other): """ Chaining of filters using the & operator. """ if isinstance(other, Always) or isinstance(self, Never): return self elif isinstance(other, Never) or isinstance(self, Always): return other else: assert isinstance(other, Filter), 'Expecting filter, got %r' % other return _and(self, other) def __or__(self, other): """ Chaining of filters using the | operator. """ if isinstance(other, Always) or isinstance(self, Never): return other elif isinstance(other, Never) or isinstance(self, Always): return self else: assert isinstance(other, Filter), 'Expecting filter, got %r' % other return _or(self, other) def __invert__(self): """ Inverting of filters using the ~ operator. """ return _Invert(self) def __bool__(self): """ By purpose, we don't allow bool(...) operations directly on a filter, because because the meaning is ambigue. 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 TypeError __nonzero__ = __bool__ # For Python 2. def getargspec(self): """ Return an Arguments object for this filter. This is used for type checking. """ return inspect.getargspec(self.__call__) 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): result = _AndList(filters) self[filters] = result return result class _OrCache(dict): """ Cache for Or operation between filters. """ def __missing__(self, filters): result = _OrList(filters) self[filters] = result return result _and_cache = _AndCache() _or_cache = _OrCache() def _and(filter1, filter2): """ And operation between two filters. """ return _and_cache[filter1, filter2] def _or(filter1, filter2): """ Or operation between two filters. """ return _or_cache[filter1, filter2] 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) # Make sure that all chained filters have the same signature. check_signatures_are_equal(all_filters) self.filters = all_filters def getargspec(self): return self.filters[0].getargspec() def __call__(self, *a, **kw): return all(f(*a, **kw) 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) # Make sure that all chained filters have the same signature. check_signatures_are_equal(all_filters) self.filters = all_filters def getargspec(self): return self.filters[0].getargspec() def __call__(self, *a, **kw): return any(f(*a, **kw) 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, *a, **kw): return not self.filter(*a, **kw) def __repr__(self): return '~%r' % self.filter def getargspec(self): return self.filter.getargspec() class Always(Filter): """ Always enable feature. """ def __call__(self, *a, **kw): return True def __invert__(self): return Never() class Never(Filter): """ Never enable feature. """ def __call__(self, *a, **kw): return False def __invert__(self): return Always() class Condition(Filter): """ Turn any callable (which takes a cli and returns a boolean) into a Filter. This can be used as a decorator:: @Condition def feature_is_active(cli): # `feature_is_active` becomes a Filter. return True :param func: Callable which takes either a :class:`~prompt_toolkit.interface.CommandLineInterface` or nothing and returns a boolean. (Depending on what it takes, this will become a :class:`.Filter` or :class:`~prompt_toolkit.filters.CLIFilter`.) """ def __init__(self, func): assert callable(func) self.func = func def __call__(self, *a, **kw): return self.func(*a, **kw) def __repr__(self): return 'Condition(%r)' % self.func def getargspec(self): return inspect.getargspec(self.func) prompt_toolkit-0.57/prompt_toolkit/search_state.py0000644000175000017500000000211312623240275024266 0ustar jonathanjonathan00000000000000from .enums import IncrementalSearchDirection from .filters import to_simple_filter __all__ = ( 'SearchState', ) class SearchState(object): """ A search 'query'. """ __slots__ = ('text', 'direction', 'ignore_case') def __init__(self, text='', direction=IncrementalSearchDirection.FORWARD, ignore_case=False): ignore_case = to_simple_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 == IncrementalSearchDirection.BACKWARD: direction = IncrementalSearchDirection.FORWARD else: direction = IncrementalSearchDirection.BACKWARD return SearchState(text=self.text, direction=direction, ignore_case=self.ignore_case) prompt_toolkit-0.57/prompt_toolkit/output.py0000644000175000017500000000623612630721662023175 0ustar jonathanjonathan00000000000000""" Interface for an output. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'Output', ) 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.terminal.vt100_output.Vt100_Output` and :class:`~prompt_toolkit.terminal.win32_output.Win32Output`. """ @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): " 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. " prompt_toolkit-0.57/prompt_toolkit/styles.py0000644000175000017500000001654112642107763023163 0ustar jonathanjonathan00000000000000""" Styling for prompt_toolkit applications. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from collections import namedtuple from pygments.token import Token from six import with_metaclass import pygments.style import pygments.styles.default __all__ = ( 'Style', 'Attrs', 'DynamicStyle', 'PygmentsStyle', 'DEFAULT_STYLE_EXTENSIONS', 'DEFAULT_STYLE', ) #: Style attributes. Attrs = namedtuple('Attrs', 'color bgcolor bold underline italic blink reverse') """ :param color: Hexadecimal string. E.g. '000000' :param bgcolor: Hexadecimal string. E.g. 'ffffff' :param bold: Boolean :param underline: Boolean :param italic: Boolean :param blink: Boolean :param reverse: Boolean """ _default_attrs = Attrs(color=None, bgcolor=None, bold=False, underline=False, italic=False, blink=False, reverse=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. ANSI_COLOR_NAMES = [ 'black', 'white', 'default', # Low intensity. 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'gray', # High intensity. (Not supported everywhere.) 'dark-gray', 'bright-red', 'bright-green', 'bright-yellow', 'bright-blue', 'bright-magenta', 'bright-cyan', ] class Style(with_metaclass(ABCMeta, object)): """ Abstract base class for prompt_toolkit styles. """ @abstractmethod def get_attrs_for_token(self, token): """ Return :class:`.Attrs` for the given token. """ @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 DynamicStyle(Style): """ 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 def get_attrs_for_token(self, token): style = self.get_style() assert isinstance(style, Style) return style.get_attrs_for_token(token) def invalidation_hash(self): return self.get_style().invalidation_hash() class PygmentsStyle(Style): """ Adaptor for using Pygments styles as a :class:`.Style`. :param pygments_style_cls: Pygments ``Style`` class. """ def __init__(self, pygments_style_cls): assert issubclass(pygments_style_cls, pygments.style.Style) self.pygments_style_cls = pygments_style_cls self._token_to_attrs_dict = None def get_attrs_for_token(self, token): try: style = self.pygments_style_cls.style_for_token(token) return Attrs(color=style['color'], bgcolor=style['bgcolor'], bold=style.get('bold', False), underline=style.get('underline', False), italic=style.get('italic', False), blink=False, reverse=False) except KeyError: return _default_attrs def invalidation_hash(self): return id(self.pygments_style_cls) @classmethod def from_defaults(cls, style_dict=None, pygments_style_cls=pygments.styles.default.DefaultStyle, include_extensions=True): """ Shortcut to create a :class:`.PygmentsStyle` instance from a Pygments dictionary and a style class. :param style_dict: Dictionary for this style. `{Token: style}`. :param pygments_style_cls: Pygments style class to start from. :param include_extensions: (`bool`) Include prompt_toolkit extensions. """ assert style_dict is None or isinstance(style_dict, dict) assert pygments_style_cls is None or issubclass(pygments_style_cls, pygments.style.Style) class _CustomStyle(pygments.styles.default.DefaultStyle): background_color = None styles = {} if pygments_style_cls is not None: styles.update(pygments_style_cls.styles) if include_extensions: styles.update(DEFAULT_STYLE_EXTENSIONS) if style_dict is not None: styles.update(style_dict) return cls(_CustomStyle) #: Styling of prompt-toolkit specific tokens, that are not know by the default #: Pygments style. DEFAULT_STYLE_EXTENSIONS = { # Highlighting of search matches in document. Token.SearchMatch: '#000000 bg:#888888', Token.SearchMatch.Current: '#ffffff bg:#aa8888 underline', # Highlighting of select text in document. Token.SelectedText: '#ffffff bg:#666666', # Highlighting of matching brackets. Token.MatchingBracket: 'bg:#aaaaff #000000', # Line numbers. Token.LineNumber: '#888888', Token.LineNumber.Current: 'bold', # Default prompt. Token.Prompt: 'bold', Token.Prompt.Arg: 'noinherit', Token.Prompt.Search: 'noinherit', Token.Prompt.Search.Text: 'bold', # Search toolbar. Token.Toolbar.Search: 'bold', Token.Toolbar.Search.Text: 'nobold', # System toolbar Token.Toolbar.System: 'bold', # "arg" toolbar. Token.Toolbar.Arg: 'bold', Token.Toolbar.Arg.Text: 'nobold', # Validation toolbar. Token.Toolbar.Validation: 'bg:#550000 #ffffff', Token.WindowTooSmall: 'bg:#550000 #ffffff', # Completions toolbar. Token.Toolbar.Completions: 'bg:#bbbbbb #000000', Token.Toolbar.Completions.Arrow: 'bg:#bbbbbb #000000 bold', Token.Toolbar.Completions.Completion: 'bg:#bbbbbb #000000', Token.Toolbar.Completions.Completion.Current: 'bg:#444444 #ffffff', # Completions menu. Token.Menu.Completions.Completion: 'bg:#bbbbbb #000000', Token.Menu.Completions.Completion.Current: 'bg:#888888 #ffffff', Token.Menu.Completions.Meta: 'bg:#999999 #000000', Token.Menu.Completions.Meta.Current: 'bg:#aaaaaa #000000', Token.Menu.Completions.MultiColumnMeta: 'bg:#aaaaaa #000000', Token.Menu.Completions.ProgressBar: 'bg:#aaaaaa', Token.Menu.Completions.ProgressButton: 'bg:#000000', # Scrollbars. Token.Scrollbar: 'bg:#444444', Token.Scrollbar.Button: 'bg:#888888', Token.Scrollbar.Arrow: 'bg:#222222 #ffffff', # Auto suggestion text. Token.AutoSuggestion: '#666666', # When Control-C has been pressed. Grayed. Token.Aborted: '#888888', } default_style_extensions = DEFAULT_STYLE_EXTENSIONS # Old name. #: The default built-in style. DEFAULT_STYLE = PygmentsStyle.from_defaults() prompt_toolkit-0.57/prompt_toolkit/key_binding/0000755000175000017500000000000012642647210023535 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/key_binding/registry.py0000644000175000017500000001201412632211675025756 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from ..filters import CLIFilter, to_cli_filter, Never from ..keys import Key from ..utils import Callback from six import text_type from collections import defaultdict __all__ = ( 'Registry', ) class _Binding(object): """ (Immutable binding class.) """ def __init__(self, keys, handler, filter=None, eager=None, invalidate_ui=True): assert isinstance(keys, tuple) assert callable(handler) assert isinstance(filter, CLIFilter) assert isinstance(eager, CLIFilter) assert isinstance(invalidate_ui, CLIFilter) self.keys = keys self.handler = handler self.filter = filter self.eager = eager self.invalidate_ui = invalidate_ui 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 Registry(object): """ Key binding registry. :: r = Registry() @r.add_binding(Keys.ControlX, Keys.ControlC, filter=INSERT) def handler(event): # Handle ControlX-ControlC key sequence. pass """ def __init__(self): self.key_bindings = [] #: (tuple of keys) -> [list of bindings handling this sequence]. self._keys_to_bindings = defaultdict(list) #: (tuple of keys) -> [list of bindings handling suffixes of this sequence]. self._keys_to_bindings_suffixes = defaultdict(list) self.on_handler_called = Callback() def add_binding(self, *keys, **kwargs): """ Decorator for annotating key bindings. :param filter: :class:`~prompt_toolkit.filters.CLIFilter` to determine when this key binding is active. :param eager: :class:`~prompt_toolkit.filters.CLIFilter` 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 invalidate_ui: :class:`~prompt_toolkit.filters.CLIFilter` or `bool`. When True (which is the default), invalidate (redraw) the user interface after handling this key binding. """ filter = to_cli_filter(kwargs.pop('filter', True)) eager = to_cli_filter(kwargs.pop('eager', False)) invalidate_ui = to_cli_filter(kwargs.pop('invalidate_ui', True)) assert not kwargs assert keys assert all(isinstance(k, (Key, text_type)) for k in keys), \ 'Key bindings should consist of Key and string (unicode) instances.' def decorator(func): # When a filter is Never, it will always stay disabled, so in that case # don't bother putting it in the registry. It will slow down every key # press otherwise. (This happens for instance when a KeyBindingManager # is used, but some set of bindings are always disabled.) if not isinstance(filter, Never): binding = _Binding(keys, func, filter=filter, eager=eager, invalidate_ui=invalidate_ui) self.key_bindings.append(binding) self._keys_to_bindings[keys].append(binding) for i in range(1, len(keys)): self._keys_to_bindings_suffixes[keys[:i]].append(binding) return func return decorator def remove_binding(self, function): """ Remove a key binding. This expects a function that was given to `add_binding` method as parameter. Raises `ValueError` when the given function was not registered before. """ assert callable(function) for b in self.key_bindings: if b.handler == function: self.key_bindings.remove(b) self._keys_to_bindings[b.keys].remove(b) for i in range(1, len(b.keys)): self._keys_to_bindings_suffixes[b.keys[:i]].remove(b) return # No key binding found for this function. Raise ValueError. raise ValueError('Binding not found: %r' % (function, )) 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. """ return self._keys_to_bindings[keys] 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 self._keys_to_bindings_suffixes[keys] prompt_toolkit-0.57/prompt_toolkit/key_binding/vi_state.py0000644000175000000000000000155712556317626025112 0ustar jonathanroot00000000000000from __future__ import unicode_literals __all__ = ( 'InputMode', 'CharacterFind', 'ViState', ) class InputMode(object): INSERT = 'vi-insert' NAVIGATION = 'vi-navigation' 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 #: The Vi mode we're currently in to. self.input_mode = InputMode.INSERT def reset(self): # Go back to insert mode. self.input_mode = InputMode.INSERT prompt_toolkit-0.57/prompt_toolkit/key_binding/bindings/0000755000175000017500000000000012642647210025332 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/key_binding/bindings/utils.py0000644000175000017500000000250012577344152027047 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from functools import wraps from prompt_toolkit.filters import CLIFilter, Always __all__ = ( 'create_handle_decorator', ) def create_handle_decorator(registry, filter=Always()): """ Create a key handle decorator, which is compatible with `Registry.handle` but has a `save_before` option, which will make sure that changes are saved to the undo stack of the `Buffer` object before every key press event. :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.) """ assert isinstance(filter, CLIFilter) def handle(*keys, **kw): save_before = kw.pop('save_before', lambda e: True) # Chain the given filter to the filter of this specific binding. if 'filter' in kw: kw['filter'] = kw['filter'] & filter else: kw['filter'] = filter def decorator(handler_func): @registry.add_binding(*keys, **kw) @wraps(handler_func) def wrapper(event): if save_before(event): event.cli.current_buffer.save_to_undo_stack() handler_func(event) return handler_func return decorator return handle prompt_toolkit-0.57/prompt_toolkit/key_binding/bindings/emacs.py0000644000175000017500000004276212642105374027007 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.buffer import SelectionType, indent, unindent from prompt_toolkit.keys import Keys from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER from prompt_toolkit.filters import CLIFilter, Always from .utils import create_handle_decorator from .scroll import scroll_page_up, scroll_page_down import prompt_toolkit.filters as filters __all__ = ( 'load_emacs_bindings', 'load_emacs_search_bindings', 'load_emacs_system_bindings', 'load_extra_emacs_page_navigation_bindings', ) def load_emacs_bindings(registry, filter=Always()): """ Some e-macs extensions. """ # Overview of Readline emacs commands: # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf assert isinstance(filter, CLIFilter) handle = create_handle_decorator(registry, filter) has_selection = filters.HasSelection() @handle(Keys.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(Keys.ControlA) def _(event): """ Start of line. """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=False) @handle(Keys.ControlB) def _(event): """ Character back. """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_left_position(count=event.arg) @handle(Keys.ControlE) def _(event): """ End of line. """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_end_of_line_position() @handle(Keys.ControlF) def _(event): """ Character forward. """ buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_right_position(count=event.arg) @handle(Keys.ControlN, filter= ~has_selection) def _(event): """ Next line. """ event.current_buffer.auto_down() @handle(Keys.ControlN, filter=has_selection) def _(event): """ Next line. """ event.current_buffer.cursor_down() @handle(Keys.ControlO, filter= ~has_selection) def _(event): """ Insert newline, but don't move the cursor. """ event.current_buffer.insert_text('\n', move_cursor=False) @handle(Keys.ControlP, filter= ~has_selection) def _(event): """ Previous line. """ event.current_buffer.auto_up(count=event.arg) @handle(Keys.ControlP, filter=has_selection) def _(event): """ Previous line. """ event.current_buffer.cursor_up(count=event.arg) @handle(Keys.ControlQ, Keys.Any, filter= ~has_selection) def _(event): """ Quoted insert. For vt100 terminals, you have to disable flow control by running ``stty -ixon``, otherwise Ctrl-Q and Ctrl-S are captured by the terminal. """ event.current_buffer.insert_text(event.data, overwrite=False) @handle(Keys.ControlY, filter= ~has_selection) @handle(Keys.ControlX, 'r', 'y', filter= ~has_selection) def _(event): """ Paste before cursor. """ event.current_buffer.paste_clipboard_data( event.cli.clipboard.get_data(), count=event.arg, before=True) @handle(Keys.ControlUnderscore, save_before=(lambda e: False), filter= ~has_selection) def _(event): """ Undo. """ event.current_buffer.undo() def handle_digit(c): """ Handle Alt + digit in the `meta_digit` method. """ @handle(Keys.Escape, c) def _(event): event.append_to_arg_count(c) for c in '0123456789': handle_digit(c) @handle(Keys.Escape, '-') def _(event): """ """ if event._arg is None: event.append_to_arg_count('-') @handle(Keys.Escape, Keys.ControlJ, filter= ~has_selection) def _(event): """ Meta + Newline: always accept input. """ b = event.current_buffer if b.accept_action.is_returnable: b.accept_action.validate_and_handle(event.cli, b) @handle(Keys.ControlSquareClose, Keys.Any) def _(event): """ When Ctl-] + a character is pressed. go to that character. """ match = event.current_buffer.document.find(event.data, in_current_line=True, count=(event.arg)) if match is not None: event.current_buffer.cursor_position += match @handle(Keys.Escape, Keys.Backspace, filter= ~has_selection) def _(event): """ Delete word backwards. """ buffer = event.current_buffer pos = buffer.document.find_start_of_previous_word(count=event.arg) if pos: deleted = buffer.delete_before_cursor(count=-pos) event.cli.clipboard.set_text(deleted) @handle(Keys.Escape, 'a', filter= ~has_selection) def _(event): """ Previous sentence. """ # TODO: pass @handle(Keys.Escape, 'c', filter= ~has_selection) def _(event): """ Capitalize the current (or following) word. """ buffer = event.current_buffer for i in range(event.arg): pos = buffer.document.find_next_word_ending() words = buffer.document.text_after_cursor[:pos] buffer.insert_text(words.title(), overwrite=True) @handle(Keys.Escape, 'd', filter= ~has_selection) def _(event): """ Delete word forwards. """ buffer = event.current_buffer pos = buffer.document.find_next_word_ending(count=event.arg) if pos: deleted = buffer.delete(count=pos) event.cli.clipboard.set_text(deleted) @handle(Keys.Escape, 'e', filter= ~has_selection) def _(event): """ Move to end of sentence. """ # TODO: pass @handle(Keys.Escape, 'f') @handle(Keys.ControlRight) def _(event): """ Cursor to end of next word. """ buffer= event.current_buffer pos = buffer.document.find_next_word_ending(count=event.arg) if pos: buffer.cursor_position += pos @handle(Keys.Escape, 'b') @handle(Keys.ControlLeft) def _(event): """ Cursor to start of previous word. """ buffer = event.current_buffer pos = buffer.document.find_previous_word_beginning(count=event.arg) if pos: buffer.cursor_position += pos @handle(Keys.Escape, 'l', filter= ~has_selection) def _(event): """ Lowercase the current (or following) word. """ buffer = event.current_buffer for i in range(event.arg): # XXX: not DRY: see meta_c and meta_u!! pos = buffer.document.find_next_word_ending() words = buffer.document.text_after_cursor[:pos] buffer.insert_text(words.lower(), overwrite=True) @handle(Keys.Escape, 't', filter= ~has_selection) def _(event): """ Swap the last two words before the cursor. """ # TODO @handle(Keys.Escape, 'u', filter= ~has_selection) def _(event): """ Uppercase the current (or following) word. """ buffer = event.current_buffer for i in range(event.arg): pos = buffer.document.find_next_word_ending() words = buffer.document.text_after_cursor[:pos] buffer.insert_text(words.upper(), overwrite=True) @handle(Keys.Escape, '.', filter= ~has_selection) def _(event): """ Rotate through the last word (white-space delimited) of the previous lines in history. """ # TODO @handle(Keys.Escape, '\\', filter= ~has_selection) def _(event): """ Delete all spaces and tabs around point. (delete-horizontal-space) """ @handle(Keys.Escape, '*', filter= ~has_selection) def _(event): """ `meta-*`: Insert all possible completions of the preceding text. """ @handle(Keys.ControlX, Keys.ControlU, save_before=(lambda e: False), filter= ~has_selection) def _(event): event.current_buffer.undo() @handle(Keys.ControlX, Keys.ControlX) def _(event): """ Move cursor back and forth between the start and end of the current line. """ buffer = event.current_buffer if buffer.document.current_char == '\n': 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(Keys.ControlSpace) def _(event): """ Start of the selection. """ # Take the current cursor position as the start of this selection. event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) @handle(Keys.ControlG, 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(Keys.ControlG, filter=has_selection) def _(event): """ Cancel selection. """ event.current_buffer.exit_selection() @handle(Keys.ControlW, filter=has_selection) @handle(Keys.ControlX, 'r', 'k', filter=has_selection) def _(event): """ Cut selected text. """ data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(data) @handle(Keys.Escape, 'w', filter=has_selection) def _(event): """ Copy selected text. """ data = event.current_buffer.copy_selection() event.cli.clipboard.set_data(data) @handle(Keys.Escape, '<', filter= ~has_selection) def _(event): """ Move to the first line in the history. """ event.current_buffer.go_to_history(0) @handle(Keys.Escape, '>', filter= ~has_selection) def _(event): """ Move to the end of the input history. This is the line we are editing. """ buffer = event.current_buffer buffer.go_to_history(len(buffer._working_lines) - 1) @handle(Keys.Escape, Keys.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(Keys.Escape, Keys.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(Keys.Escape, '/', filter= ~has_selection) def _(event): """ M-/: Complete. """ b = event.current_buffer if b.complete_state: b.complete_next() else: event.cli.start_completion(select_first=True) @handle(Keys.ControlC, '>', 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(Keys.ControlC, '<', 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) def load_emacs_open_in_editor_bindings(registry, filter=None): """ Pressing C-X C-E will open the buffer in an external editor. """ handle = create_handle_decorator(registry, filter) has_selection = filters.HasSelection() @handle(Keys.ControlX, Keys.ControlE, filter= ~has_selection) def _(event): """ Open editor. """ event.current_buffer.open_in_editor(event.cli) def load_emacs_system_bindings(registry, filter=None): handle = create_handle_decorator(registry, filter) has_focus = filters.HasFocus(SYSTEM_BUFFER) @handle(Keys.Escape, '!', filter= ~has_focus) def _(event): """ M-'!' opens the system prompt. """ event.cli.push_focus(SYSTEM_BUFFER) @handle(Keys.Escape, filter=has_focus) @handle(Keys.ControlG, filter=has_focus) @handle(Keys.ControlC, filter=has_focus) def _(event): """ Cancel system prompt. """ event.cli.buffers[SYSTEM_BUFFER].reset() event.cli.pop_focus() @handle(Keys.ControlJ, filter=has_focus) def _(event): """ Run system command. """ system_line = event.cli.buffers[SYSTEM_BUFFER] event.cli.run_system_command(system_line.text) system_line.reset(append_to_history=True) # Focus previous buffer again. event.cli.pop_focus() def load_emacs_search_bindings(registry, get_search_state=None, filter=None): handle = create_handle_decorator(registry, filter) has_focus = filters.HasFocus(SEARCH_BUFFER) assert get_search_state is None or callable(get_search_state) if not get_search_state: def get_search_state(cli): return cli.search_state @handle(Keys.ControlG, filter=has_focus) @handle(Keys.ControlC, filter=has_focus) # NOTE: the reason for not also binding Escape to this one, is that we want # Alt+Enter to accept input directly in incremental search mode. def _(event): """ Abort an incremental search and restore the original line. """ search_buffer = event.cli.buffers[SEARCH_BUFFER] search_buffer.reset() event.cli.pop_focus() @handle(Keys.ControlJ, filter=has_focus) def _(event): """ When enter pressed in isearch, quit isearch mode. (Multiline isearch would be too complicated.) """ input_buffer = event.cli.buffers.previous(event.cli) search_buffer = event.cli.buffers[SEARCH_BUFFER] # Update search state. if search_buffer.text: get_search_state(event.cli).text = search_buffer.text # Apply search. input_buffer.apply_search(get_search_state(event.cli), include_current_position=True) # Add query to history of search line. search_buffer.append_to_history() search_buffer.reset() # Focus previous document again. event.cli.pop_focus() @handle(Keys.ControlR, filter= ~has_focus) def _(event): get_search_state(event.cli).direction = IncrementalSearchDirection.BACKWARD event.cli.push_focus(SEARCH_BUFFER) @handle(Keys.ControlS, filter= ~has_focus) def _(event): get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD event.cli.push_focus(SEARCH_BUFFER) @handle(Keys.ControlR, filter=has_focus) @handle(Keys.Up, filter=has_focus) def _(event): # Update search_state. search_state = get_search_state(event.cli) direction_changed = search_state.direction != IncrementalSearchDirection.BACKWARD search_state.text = event.cli.buffers[SEARCH_BUFFER].text search_state.direction = IncrementalSearchDirection.BACKWARD # Apply search to current buffer. if not direction_changed: input_buffer = event.cli.buffers.previous(event.cli) input_buffer.apply_search(search_state, include_current_position=False, count=event.arg) @handle(Keys.ControlS, filter=has_focus) @handle(Keys.Down, filter=has_focus) def _(event): # Update search_state. search_state = get_search_state(event.cli) direction_changed = search_state.direction != IncrementalSearchDirection.FORWARD search_state.text = event.cli.buffers[SEARCH_BUFFER].text search_state.direction = IncrementalSearchDirection.FORWARD # Apply search to current buffer. if not direction_changed: input_buffer = event.cli.buffers.previous(event.cli) input_buffer.apply_search(search_state, include_current_position=False, count=event.arg) def load_extra_emacs_page_navigation_bindings(registry, filter=None): """ Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them. """ handle = create_handle_decorator(registry, filter) handle(Keys.ControlV)(scroll_page_down) handle(Keys.PageDown)(scroll_page_down) handle(Keys.Escape, 'v')(scroll_page_up) handle(Keys.PageUp)(scroll_page_up) prompt_toolkit-0.57/prompt_toolkit/key_binding/bindings/vi.py0000644000175000017500000014227712642105374026337 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.buffer import ClipboardData, indent, unindent from prompt_toolkit.document import Document from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER from prompt_toolkit.filters import Filter, Condition, HasArg, Always, to_cli_filter, IsReadOnly from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode from prompt_toolkit.keys import Keys from prompt_toolkit.layout.utils import find_window_for_buffer_name from prompt_toolkit.selection import SelectionType from .utils import create_handle_decorator from .scroll import scroll_forward, scroll_backward, scroll_half_page_up, scroll_half_page_down, scroll_one_line_up, scroll_one_line_down, scroll_page_up, scroll_page_down import prompt_toolkit.filters as filters import codecs __all__ = ( 'load_vi_bindings', 'load_vi_search_bindings', 'load_vi_system_bindings', 'load_extra_vi_page_navigation_bindings', ) class ViStateFilter(Filter): """ Filter to enable some key bindings only in a certain Vi input mode. :param get_vi_state: Callable that takes a `CommandLineInterface` and returns a :class:`~prompt_toolkit.key_binding.vi_state.ViState` instance. """ # Note: The reason for making get_vi_state a callable, is that this way, # the registry of key bindings becomes more stateless and can be # reused for multiple CommandLineInterface instances. def __init__(self, get_vi_state, mode): assert callable(get_vi_state) self.get_vi_state = get_vi_state self.mode = mode def __call__(self, cli): return self.get_vi_state(cli).input_mode == self.mode class CursorRegion(object): """ Return struct for functions wrapped in ``change_delete_move_yank_handler``. Both `start` and `end` are relative to the current cursor position. """ def __init__(self, start, end=0): self.start = start self.end = end 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 load_vi_bindings(registry, get_vi_state, enable_visual_key=Always(), get_search_state=None, filter=None): """ Vi extensions. # Overview of Readline Vi commands: # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf :param get_vi_state: Callable that takes a CommandLineInterface instances and returns the used ViState. :param enable_visual_key: Filter to enable lowercase 'v' bindings. A reason to disable these are to support open-in-editor functionality. These key bindings conflict. :param get_search_state: None or a callable that takes a CommandLineInterface and returns a SearchState. """ # 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. assert callable(get_vi_state) enable_visual_key = to_cli_filter(enable_visual_key) # Default get_search_state. if get_search_state is None: def get_search_state(cli): return cli.search_state handle = create_handle_decorator(registry, filter) insert_mode = ViStateFilter(get_vi_state, InputMode.INSERT) & ~ filters.HasSelection() navigation_mode = ViStateFilter(get_vi_state, InputMode.NAVIGATION) & ~ filters.HasSelection() replace_mode = ViStateFilter(get_vi_state, InputMode.REPLACE) & ~ filters.HasSelection() selection_mode = filters.HasSelection() vi_transform_functions = [ # Rot 13 transformation (('g', '?'), lambda string: codecs.encode(string, 'rot_13')), # To lowercase (('g', 'u'), lambda string: string.lower()), # To uppercase. (('g', 'U'), lambda string: string.upper()), # Swap case. # (XXX: If we would implement 'tildeop', the 'g' prefix is not required.) (('g', '~'), lambda string: string.swapcase()), ] def check_cursor_position(event): """ After every command, make sure that if we are in navigation mode, we never put the cursor after the last character of a line. (Unless it's an empty line.) """ buffer = event.current_buffer if ( (filter is None or filter(event.cli)) and # First make sure that this key bindings are active. get_vi_state(event.cli).input_mode == InputMode.NAVIGATION and buffer.document.is_cursor_at_the_end_of_line and len(buffer.document.current_line) > 0): buffer.cursor_position -= 1 registry.on_handler_called += check_cursor_position @handle(Keys.Escape) def _(event): """ Escape goes to vi navigation mode. """ buffer = event.current_buffer vi_state = get_vi_state(event.cli) 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=selection_mode) def _(event): """ Arrow up in selection mode. """ event.current_buffer.cursor_up(count=event.arg) @handle('j', filter=selection_mode) def _(event): """ Arrow down in selection mode. """ event.current_buffer.cursor_down(count=event.arg) @handle('k', filter=navigation_mode) @handle(Keys.Up, filter=navigation_mode) @handle(Keys.ControlP, filter=navigation_mode) def _(event): """ Arrow up and ControlP in navigation mode go up. """ b = event.current_buffer b.auto_up(count=event.arg) @handle('j', filter=navigation_mode) @handle(Keys.Down, filter=navigation_mode) @handle(Keys.ControlN, filter=navigation_mode) def _(event): """ Arrow down and Control-N in navigation mode. """ b = event.current_buffer b.auto_down(count=event.arg) @handle(Keys.Backspace, filter=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(Keys.ControlV, Keys.Any, filter=insert_mode) def _(event): """ Insert a character literally (quoted insert). """ event.current_buffer.insert_text(event.data, overwrite=False) @handle(Keys.ControlN, filter=insert_mode) def _(event): b = event.current_buffer if b.complete_state: b.complete_next() else: event.cli.start_completion(select_first=True) @handle(Keys.ControlP, filter=insert_mode) def _(event): """ Control-P: To previous completion. """ b = event.current_buffer if b.complete_state: b.complete_previous() else: event.cli.start_completion(select_last=True) @handle(Keys.ControlY, filter=insert_mode) def _(event): """ Accept current completion. """ event.current_buffer.complete_state = None @handle(Keys.ControlE, filter=insert_mode) def _(event): """ Cancel completion. Go back to originally typed text. """ event.current_buffer.cancel_completion() @handle(Keys.ControlJ, filter=navigation_mode) def _(event): """ In navigation mode, pressing enter will always return the input. """ b = event.current_buffer if b.accept_action.is_returnable: b.accept_action.validate_and_handle(event.cli, b) # ** In navigation mode ** # List of navigation commands: http://hea-www.harvard.edu/~fine/Tech/vi.html @handle(Keys.Insert, filter=navigation_mode) def _(event): " Presing the Insert key. " get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('a', filter=navigation_mode & ~IsReadOnly()) # ~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() get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('A', filter=navigation_mode & ~IsReadOnly()) def _(event): event.current_buffer.cursor_position += event.current_buffer.document.get_end_of_line_position() get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('C', filter=navigation_mode & ~IsReadOnly()) 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.cli.clipboard.set_text(deleted) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('c', 'c', filter=navigation_mode & ~IsReadOnly()) @handle('S', filter=navigation_mode & ~IsReadOnly()) 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.cli.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()) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('D', filter=navigation_mode) def _(event): buffer = event.current_buffer deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) event.cli.clipboard.set_text(deleted) @handle('d', 'd', filter=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.cli.clipboard.set_data(ClipboardData(deleted, SelectionType.LINES)) @handle('i', filter=navigation_mode & ~IsReadOnly()) def _(event): get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('I', filter=navigation_mode & ~IsReadOnly()) def _(event): get_vi_state(event.cli).input_mode = InputMode.INSERT event.current_buffer.cursor_position += event.current_buffer.document.get_start_of_line_position(after_whitespace=True) @handle('J', filter=navigation_mode) def _(event): """ Join lines. """ for i in range(event.arg): event.current_buffer.join_next_line() @handle('J', filter=selection_mode) def _(event): """ Join selected lines. """ event.current_buffer.join_selected_lines() @handle('n', filter=navigation_mode) def _(event): # XXX: use `change_delete_move_yank_handler` """ Search next. """ event.current_buffer.apply_search( get_search_state(event.cli), include_current_position=False, count=event.arg) @handle('N', filter=navigation_mode) def _(event): # TODO: use `change_delete_move_yank_handler` """ Search previous. """ event.current_buffer.apply_search( ~get_search_state(event.cli), include_current_position=False, count=event.arg) @handle('p', filter=navigation_mode) def _(event): """ Paste after """ event.current_buffer.paste_clipboard_data( event.cli.clipboard.get_data(), count=event.arg) @handle('P', filter=navigation_mode) def _(event): """ Paste before """ event.current_buffer.paste_clipboard_data( event.cli.clipboard.get_data(), before=True, count=event.arg) @handle('r', Keys.Any, filter=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=navigation_mode) def _(event): """ Go to 'replace'-mode. """ get_vi_state(event.cli).input_mode = InputMode.REPLACE @handle('s', filter=navigation_mode & ~IsReadOnly()) def _(event): """ Substitute with new text (Delete character(s) and go to insert mode.) """ text = event.current_buffer.delete(count=event.arg) event.cli.clipboard.set_text(text) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('u', filter=navigation_mode, save_before=(lambda e: False)) def _(event): for i in range(event.arg): event.current_buffer.undo() @handle('V', filter=navigation_mode) def _(event): """ Start lines selection. """ event.current_buffer.start_selection(selection_type=SelectionType.LINES) @handle(Keys.ControlV, filter=navigation_mode) def _(event): " Enter block selection mode. " event.current_buffer.start_selection(selection_type=SelectionType.BLOCK) @handle('V', filter=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=navigation_mode & enable_visual_key) def _(event): " Enter character selection mode. " event.current_buffer.start_selection(selection_type=SelectionType.CHARACTERS) @handle('v', filter=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(Keys.ControlV, filter=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=selection_mode) @handle('a', 'W', filter=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=navigation_mode) def _(event): """ Delete character. """ text = event.current_buffer.delete(count=event.arg) event.cli.clipboard.set_text(text) @handle('x', filter=selection_mode) @handle('d', filter=selection_mode) def _(event): """ Cut selection. """ clipboard_data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(clipboard_data) @handle('c', filter=selection_mode & ~IsReadOnly()) def _(event): """ Change selection (cut and go to insert mode). """ clipboard_data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(clipboard_data) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('y', filter=selection_mode) def _(event): """ Copy selection. """ clipboard_data = event.current_buffer.copy_selection() event.cli.clipboard.set_data(clipboard_data) @handle('X', filter=navigation_mode) def _(event): text = event.current_buffer.delete_before_cursor() event.cli.clipboard.set_text(text) @handle('y', 'y', filter=navigation_mode) @handle('Y', filter=navigation_mode) def _(event): """ Yank the whole line. """ text = '\n'.join(event.current_buffer.document.lines_from_current[:event.arg]) event.cli.clipboard.set_data(ClipboardData(text, SelectionType.LINES)) @handle('+', filter=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=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=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=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('>', filter=selection_mode) def _(event): """ Indent selection """ buffer = event.current_buffer selection_type = buffer.selection_state.type if selection_type == SelectionType.LINES: 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('<', filter=selection_mode) def _(event): """ Unindent selection """ buffer = event.current_buffer selection_type = buffer.selection_state.type if selection_type == SelectionType.LINES: 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) @handle('O', filter=navigation_mode & ~IsReadOnly()) def _(event): """ Open line above and enter insertion mode """ event.current_buffer.insert_line_above( copy_margin=not event.cli.in_paste_mode) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('o', filter=navigation_mode & ~IsReadOnly()) def _(event): """ Open line below and enter insertion mode """ event.current_buffer.insert_line_below( copy_margin=not event.cli.in_paste_mode) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle('~', filter=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': c = (c.upper() if c.islower() else c.lower()) buffer.insert_text(c, overwrite=True) @handle('#', filter=navigation_mode) def _(event): """ Go to previous occurence of this word. """ b = event.cli.current_buffer search_state = get_search_state(event.cli) search_state.text = b.document.get_word_under_cursor() search_state.direction = IncrementalSearchDirection.BACKWARD b.apply_search(search_state, count=event.arg, include_current_position=False) @handle('*', filter=navigation_mode) def _(event): """ Go to next occurence of this word. """ b = event.cli.current_buffer search_state = get_search_state(event.cli) search_state.text = b.document.get_word_under_cursor() search_state.direction = IncrementalSearchDirection.FORWARD b.apply_search(search_state, count=event.arg, include_current_position=False) @handle('(', filter=navigation_mode) def _(event): # TODO: go to begin of sentence. pass @handle(')', filter=navigation_mode) def _(event): # TODO: go to end of sentence. pass def change_delete_move_yank_handler(*keys, **kw): """ Register a change/delete/move/yank handlers. e.g. 'dw'/'cw'/'w'/'yw' The decorated function should return a ``CursorRegion``. This decorator will create both the 'change', 'delete' and move variants, based on that ``CursorRegion``. When there is nothing selected yet, this will also handle the "visual" binding. E.g. 'viw' should select the current word. """ no_move_handler = kw.pop('no_move_handler', False) # TODO: Also do '>' and '<' indent/unindent operators. # TODO: Also "gq": text formatting # See: :help motion.txt def decorator(func): if not no_move_handler: @handle(*keys, filter=navigation_mode|selection_mode) def move(event): """ Create move handler. """ region = func(event) event.current_buffer.cursor_position += region.start def create_transform_handler(transform_func, *a): @handle(*(a + keys), filter=navigation_mode) def _(event): """ Apply transformation (uppercase, lowercase, rot13, swap case). """ region = func(event) start, end = region.sorted() buffer = event.current_buffer # Transform. buffer.transform_region( buffer.cursor_position + start, buffer.cursor_position + end, transform_func) # Move cursor buffer.cursor_position += (region.end or region.start) for k, f in vi_transform_functions: create_transform_handler(f, *k) @handle('y', *keys, filter=navigation_mode) def yank_handler(event): """ Create yank handler. """ region = func(event) buffer = event.current_buffer start, end = region.sorted() substring = buffer.text[buffer.cursor_position + start: buffer.cursor_position + end] if substring: event.cli.clipboard.set_text(substring) def create(delete_only): """ Create delete and change handlers. """ @handle('cd'[delete_only], *keys, filter=navigation_mode & ~IsReadOnly()) @handle('cd'[delete_only], *keys, filter=navigation_mode & ~IsReadOnly()) def _(event): region = func(event) deleted = '' buffer = event.current_buffer if region: start, end = region.sorted() # Move to the start of the region. buffer.cursor_position += start # Delete until end of region. deleted = buffer.delete(count=end-start) # Set deleted/changed text to clipboard. if deleted: event.cli.clipboard.set_text(deleted) # Only go back to insert mode in case of 'change'. if not delete_only: get_vi_state(event.cli).input_mode = InputMode.INSERT create(True) create(False) return func return decorator @change_delete_move_yank_handler('b') def _(event): """ Move one word or token left. """ return CursorRegion(event.current_buffer.document.find_start_of_previous_word(count=event.arg) or 0) @change_delete_move_yank_handler('B') def _(event): """ Move one non-blank word left """ return CursorRegion(event.current_buffer.document.find_start_of_previous_word(count=event.arg, WORD=True) or 0) @change_delete_move_yank_handler('$') def key_dollar(event): """ 'c$', 'd$' and '$': Delete/change/move until end of line. """ return CursorRegion(event.current_buffer.document.get_end_of_line_position()) @change_delete_move_yank_handler('w') def _(event): """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. """ return CursorRegion(event.current_buffer.document.find_next_word_beginning(count=event.arg) or event.current_buffer.document.get_end_of_document_position()) @change_delete_move_yank_handler('W') def _(event): """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. """ return CursorRegion(event.current_buffer.document.find_next_word_beginning(count=event.arg, WORD=True) or event.current_buffer.document.get_end_of_document_position()) @change_delete_move_yank_handler('e') def _(event): """ End of 'word': 'ce', 'de', 'e' """ end = event.current_buffer.document.find_next_word_ending(count=event.arg) return CursorRegion(end - 1 if end else 0) @change_delete_move_yank_handler('E') def _(event): """ End of 'WORD': 'cE', 'dE', 'E' """ end = event.current_buffer.document.find_next_word_ending(count=event.arg, WORD=True) return CursorRegion(end - 1 if end else 0) @change_delete_move_yank_handler('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 CursorRegion(start, end) @change_delete_move_yank_handler('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 CursorRegion(start, end) @change_delete_move_yank_handler('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 CursorRegion(start, end) @change_delete_move_yank_handler('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 CursorRegion(start, end) @change_delete_move_yank_handler('^') def key_circumflex(event): """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """ return CursorRegion(event.current_buffer.document.get_start_of_line_position(after_whitespace=True)) @change_delete_move_yank_handler('0', no_move_handler=True) 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 CursorRegion(event.current_buffer.document.get_start_of_line_position(after_whitespace=False)) def create_ci_ca_handles(ci_start, ci_end, inner): # TODO: 'dab', 'dib', (brackets or block) 'daB', 'diB', Braces. # 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. """ @change_delete_move_yank_handler('ai'[inner], ci_start, no_move_handler=True) @change_delete_move_yank_handler('ai'[inner], ci_end, no_move_handler=True) def _(event): 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) if start is not None and end is not None: offset = 0 if inner else 1 return CursorRegion(start + 1 - offset, end + offset) else: # Nothing found. return CursorRegion(0) for inner in (False, True): for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"), ('[', ']'), ('<', '>'), ('{', '}'), ('(', ')')]: create_ci_ca_handles(ci_start, ci_end, inner) @change_delete_move_yank_handler('{') def _(event): """ Move to previous blank-line separated section. Implements '{', 'c{', 'd{', 'y{' """ def match_func(text): return not text or text.isspace() line_index = event.current_buffer.document.find_previous_matching_line( match_func=match_func, count=event.arg) if line_index: index = event.current_buffer.document.get_cursor_up_position(count=-line_index) else: index = 0 return CursorRegion(index) @change_delete_move_yank_handler('}') def _(event): """ Move to next blank-line separated section. Implements '}', 'c}', 'd}', 'y}' """ def match_func(text): return not text or text.isspace() line_index = event.current_buffer.document.find_next_matching_line( match_func=match_func, count=event.arg) if line_index: index = event.current_buffer.document.get_cursor_down_position(count=line_index) else: index = 0 return CursorRegion(index) @change_delete_move_yank_handler('f', Keys.Any) def _(event): """ Go to next occurance of character. Typing 'fx' will move the cursor to the next occurance of character. 'x'. """ get_vi_state(event.cli).last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find(event.data, in_current_line=True, count=event.arg) return CursorRegion(match or 0) @change_delete_move_yank_handler('F', Keys.Any) def _(event): """ Go to previous occurance of character. Typing 'Fx' will move the cursor to the previous occurance of character. 'x'. """ get_vi_state(event.cli).last_character_find = CharacterFind(event.data, True) return CursorRegion(event.current_buffer.document.find_backwards(event.data, in_current_line=True, count=event.arg) or 0) @change_delete_move_yank_handler('t', Keys.Any) def _(event): """ Move right to the next occurance of c, then one char backward. """ get_vi_state(event.cli).last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find(event.data, in_current_line=True, count=event.arg) return CursorRegion(match - 1 if match else 0) @change_delete_move_yank_handler('T', Keys.Any) def _(event): """ Move left to the previous occurance of c, then one char forward. """ get_vi_state(event.cli).last_character_find = CharacterFind(event.data, True) match = event.current_buffer.document.find_backwards(event.data, in_current_line=True, count=event.arg) return CursorRegion(match + 1 if match else 0) def repeat(reverse): """ Create ',' and ';' commands. """ @change_delete_move_yank_handler(',' if reverse else ';') def _(event): # Repeat the last 'f'/'F'/'t'/'T' command. pos = 0 vi_state = get_vi_state(event.cli) 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) return CursorRegion(pos or 0) repeat(True) repeat(False) @change_delete_move_yank_handler('h') @change_delete_move_yank_handler(Keys.Left) def _(event): """ Implements 'ch', 'dh', 'h': Cursor left. """ return CursorRegion(event.current_buffer.document.get_cursor_left_position(count=event.arg)) @change_delete_move_yank_handler('j', no_move_handler=True) def _(event): """ Implements 'cj', 'dj', 'j', ... Cursor up. """ return CursorRegion(event.current_buffer.document.get_cursor_down_position(count=event.arg)) @change_delete_move_yank_handler('k', no_move_handler=True) def _(event): """ Implements 'ck', 'dk', 'k', ... Cursor up. """ return CursorRegion(event.current_buffer.document.get_cursor_up_position(count=event.arg)) @change_delete_move_yank_handler('l') @change_delete_move_yank_handler(' ') @change_delete_move_yank_handler(Keys.Right) def _(event): """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """ return CursorRegion(event.current_buffer.document.get_cursor_right_position(count=event.arg)) @change_delete_move_yank_handler('H') def _(event): """ Moves to the start of the visible region. (Below the scroll offset.) Implements 'cH', 'dH', 'H'. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.current_buffer if w: # 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 CursorRegion(pos) @change_delete_move_yank_handler('M') def _(event): """ Moves cursor to the vertical center of the visible region. Implements 'cM', 'dM', 'M'. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.current_buffer if w: # 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 CursorRegion(pos) @change_delete_move_yank_handler('L') def _(event): """ Moves to the end of the visible region. (Above the scroll offset.) """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.current_buffer if w: # 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 CursorRegion(pos) @handle('z', '+', filter=navigation_mode|selection_mode) @handle('z', 't', filter=navigation_mode|selection_mode) @handle('z', Keys.ControlJ, filter=navigation_mode|selection_mode) def _(event): """ Scrolls the window to makes the current line the first line in the visible region. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w and w.render_info: # Calculate the offset that we need in order to position the row # containing the cursor in the center. cursor_position_row = b.document.cursor_position_row render_row = w.render_info.input_line_to_screen_line.get(cursor_position_row) if render_row is not None: w.vertical_scroll = max(0, render_row) @handle('z', '-', filter=navigation_mode|selection_mode) @handle('z', 'b', filter=navigation_mode|selection_mode) def _(event): """ Scrolls the window to makes the current line the last line in the visible region. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w and w.render_info: # Calculate the offset that we need in order to position the row # containing the cursor in the center. cursor_position_row = b.document.cursor_position_row render_row = w.render_info.input_line_to_screen_line.get(cursor_position_row) if render_row is not None: w.vertical_scroll = max(0, (render_row - w.render_info.window_height)) @handle('z', 'z', filter=navigation_mode|selection_mode) def _(event): """ Center Window vertically around cursor. """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.current_buffer if w and w.render_info: # Calculate the offset that we need in order to position the row # containing the cursor in the center. cursor_position_row = b.document.cursor_position_row render_row = w.render_info.input_line_to_screen_line.get(cursor_position_row) if render_row is not None: w.vertical_scroll = max(0, int(render_row - w.render_info.window_height / 2)) @change_delete_move_yank_handler('%') 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 / 100), 0) return CursorRegion(absolute_index - buffer.document.cursor_position) else: return CursorRegion(0) # Do nothing. else: # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). return CursorRegion(buffer.document.matching_bracket_position) @change_delete_move_yank_handler('|') def _(event): # Move to the n-th column (you may specify the argument n by typing # it on number keys, for example, 20|). return CursorRegion(event.current_buffer.document.get_column_cursor_position(event.arg)) @change_delete_move_yank_handler('g', 'g') def _(event): """ Implements 'gg', 'cgg', 'ygg' """ d = event.current_buffer.document if event._arg: # Move to the given line. return CursorRegion(d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position) else: # Move to the top of the input. return CursorRegion(d.get_start_of_document_position()) @change_delete_move_yank_handler('g', '_') def _(event): """ Go to last non-blank of line. 'g_', 'cg_', 'yg_', etc.. """ return CursorRegion( event.current_buffer.document.last_non_blank_of_current_line_position()) @change_delete_move_yank_handler('g', 'e') def _(event): """ Go to last character of previous word. 'ge', 'cge', 'yge', etc.. """ return CursorRegion( event.current_buffer.document.find_start_of_previous_word(count=event.arg) or 0) @change_delete_move_yank_handler('g', 'E') def _(event): """ Go to last character of previous WORD. 'gE', 'cgE', 'ygE', etc.. """ return CursorRegion( event.current_buffer.document.find_start_of_previous_word( count=event.arg, WORD=True) or 0) @change_delete_move_yank_handler('G') def _(event): """ Go to the end of the document. (If no arg has been given.) """ return CursorRegion(len(event.current_buffer.document.text_after_cursor)) @handle('G', filter=HasArg()) 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) @handle(Keys.Any, filter=navigation_mode) @handle(Keys.Any, filter=selection_mode) def _(event): """ Always handle numberics in navigation mode as arg. """ if event.data in '123456789' or (event._arg and event.data == '0'): event.append_to_arg_count(event.data) elif event.data == '0': buffer = event.current_buffer buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=False) @handle(Keys.Any, filter=replace_mode) def _(event): """ Insert data at cursor position. """ event.current_buffer.insert_text(event.data, overwrite=True) def create_selection_transform_handler(keys, transform_func): """ Apply transformation on selection (uppercase, lowercase, rot13, swap case). """ @handle(*keys, filter=selection_mode) def _(event): range = event.current_buffer.document.selection_range() if range: event.current_buffer.transform_region(range[0], range[1], transform_func) for k, f in vi_transform_functions: create_selection_transform_handler(k, f) @handle(Keys.ControlX, Keys.ControlL, filter=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(Keys.ControlX, Keys.ControlF, filter=insert_mode) def _(event): """ Complete file names. """ # TODO pass def load_vi_open_in_editor_bindings(registry, get_vi_state, filter=None): """ Pressing 'v' in navigation mode will open the buffer in an external editor. """ assert callable(get_vi_state) navigation_mode = ViStateFilter(get_vi_state, InputMode.NAVIGATION) & ~ filters.HasSelection() handle = create_handle_decorator(registry, filter) @handle('v', filter=navigation_mode) def _(event): event.current_buffer.open_in_editor(event.cli) def load_vi_system_bindings(registry, get_vi_state, filter=None): assert callable(get_vi_state) has_focus = filters.HasFocus(SYSTEM_BUFFER) navigation_mode = ViStateFilter(get_vi_state, InputMode.NAVIGATION) & ~ filters.HasSelection() handle = create_handle_decorator(registry, filter) @handle('!', filter=~has_focus & navigation_mode) def _(event): """ '!' opens the system prompt. """ event.cli.push_focus(SYSTEM_BUFFER) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle(Keys.Escape, filter=has_focus) @handle(Keys.ControlC, filter=has_focus) def _(event): """ Cancel system prompt. """ get_vi_state(event.cli).input_mode = InputMode.NAVIGATION event.cli.buffers[SYSTEM_BUFFER].reset() event.cli.pop_focus() @handle(Keys.ControlJ, filter=has_focus) def _(event): """ Run system command. """ get_vi_state(event.cli).input_mode = InputMode.NAVIGATION system_buffer = event.cli.buffers[SYSTEM_BUFFER] event.cli.run_system_command(system_buffer.text) system_buffer.reset(append_to_history=True) # Focus previous buffer again. event.cli.pop_focus() def load_vi_search_bindings(registry, get_vi_state, get_search_state=None, filter=None, search_buffer_name=SEARCH_BUFFER): assert callable(get_vi_state) # Callable that takes a CLI and returns a ViState. assert get_search_state is None or callable(get_search_state) if not get_search_state: def get_search_state(cli): return cli.search_state has_focus = filters.HasFocus(search_buffer_name) navigation_mode = ~has_focus & (ViStateFilter(get_vi_state, InputMode.NAVIGATION) | filters.HasSelection()) handle = create_handle_decorator(registry, filter) @handle('/', filter=navigation_mode) @handle(Keys.ControlS, filter=~has_focus) def _(event): """ Vi-style forward search. """ # Set the ViState. get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD get_vi_state(event.cli).input_mode = InputMode.INSERT # Focus search buffer. event.cli.push_focus(search_buffer_name) @handle('?', filter=navigation_mode) @handle(Keys.ControlR, filter=~has_focus) def _(event): """ Vi-style backward search. """ # Set the ViState. get_search_state(event.cli).direction = IncrementalSearchDirection.BACKWARD # Focus search buffer. event.cli.push_focus(search_buffer_name) get_vi_state(event.cli).input_mode = InputMode.INSERT @handle(Keys.ControlJ, filter=has_focus) def _(event): """ Apply the search. (At the / or ? prompt.) """ input_buffer = event.cli.buffers.previous(event.cli) search_buffer = event.cli.buffers[search_buffer_name] # Update search state. if search_buffer.text: get_search_state(event.cli).text = search_buffer.text # Apply search. input_buffer.apply_search(get_search_state(event.cli)) # Add query to history of search line. search_buffer.append_to_history() search_buffer.reset() # Focus previous document again. get_vi_state(event.cli).input_mode = InputMode.NAVIGATION event.cli.pop_focus() def search_buffer_is_empty(cli): """ Returns True when the search buffer is empty. """ return cli.buffers[search_buffer_name].text == '' @handle(Keys.Escape, filter=has_focus) @handle(Keys.ControlC, filter=has_focus) @handle(Keys.Backspace, filter=has_focus & Condition(search_buffer_is_empty)) def _(event): """ Cancel search. """ get_vi_state(event.cli).input_mode = InputMode.NAVIGATION event.cli.pop_focus() event.cli.buffers[search_buffer_name].reset() def load_extra_vi_page_navigation_bindings(registry, filter=None): """ Key bindings, for scrolling up and down through pages. This are separate bindings, because GNU readline doesn't have them. """ handle = create_handle_decorator(registry, filter) handle(Keys.ControlF)(scroll_forward) handle(Keys.ControlB)(scroll_backward) handle(Keys.ControlD)(scroll_half_page_down) handle(Keys.ControlU)(scroll_half_page_up) handle(Keys.ControlE)(scroll_one_line_down) handle(Keys.ControlY)(scroll_one_line_up) handle(Keys.PageDown)(scroll_page_down) handle(Keys.PageUp)(scroll_page_up) prompt_toolkit-0.57/prompt_toolkit/key_binding/bindings/basic.py0000644000175000017500000003644012642300315026764 0ustar jonathanjonathan00000000000000# pylint: disable=function-redefined from __future__ import unicode_literals from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.filters import CLIFilter, Always, HasSelection, Condition from prompt_toolkit.keys import Keys from prompt_toolkit.layout.screen import Point from prompt_toolkit.mouse_events import MouseEventTypes, MouseEvent from prompt_toolkit.renderer import HeightIsUnknownError from prompt_toolkit.utils import suspend_to_background_supported, is_windows from .utils import create_handle_decorator __all__ = ( 'load_basic_bindings', 'load_abort_and_exit_bindings', 'load_basic_system_bindings', 'load_auto_suggestion_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(registry, filter=Always()): assert isinstance(filter, CLIFilter) handle = create_handle_decorator(registry, filter) has_selection = HasSelection() @handle(Keys.ControlA) @handle(Keys.ControlB) @handle(Keys.ControlC) @handle(Keys.ControlD) @handle(Keys.ControlE) @handle(Keys.ControlF) @handle(Keys.ControlG) @handle(Keys.ControlH) @handle(Keys.ControlI) @handle(Keys.ControlJ) @handle(Keys.ControlK) @handle(Keys.ControlL) @handle(Keys.ControlM) @handle(Keys.ControlN) @handle(Keys.ControlO) @handle(Keys.ControlP) @handle(Keys.ControlQ) @handle(Keys.ControlR) @handle(Keys.ControlS) @handle(Keys.ControlT) @handle(Keys.ControlU) @handle(Keys.ControlV) @handle(Keys.ControlW) @handle(Keys.ControlX) @handle(Keys.ControlY) @handle(Keys.ControlZ) @handle(Keys.F1) @handle(Keys.F2) @handle(Keys.F3) @handle(Keys.F4) @handle(Keys.F5) @handle(Keys.F6) @handle(Keys.F7) @handle(Keys.F8) @handle(Keys.F9) @handle(Keys.F10) @handle(Keys.F11) @handle(Keys.F12) @handle(Keys.F13) @handle(Keys.F14) @handle(Keys.F15) @handle(Keys.F16) @handle(Keys.F17) @handle(Keys.F18) @handle(Keys.F19) @handle(Keys.F20) @handle(Keys.ControlSpace) @handle(Keys.ControlBackslash) @handle(Keys.ControlSquareClose) @handle(Keys.ControlCircumflex) @handle(Keys.ControlUnderscore) @handle(Keys.Backspace) @handle(Keys.Up) @handle(Keys.Down) @handle(Keys.Right) @handle(Keys.Left) @handle(Keys.Home) @handle(Keys.End) @handle(Keys.Delete) @handle(Keys.ShiftDelete) @handle(Keys.PageUp) @handle(Keys.PageDown) @handle(Keys.BackTab) @handle(Keys.Tab) @handle(Keys.ControlLeft) @handle(Keys.ControlRight) @handle(Keys.ControlUp) @handle(Keys.ControlDown) @handle(Keys.Insert) 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 @handle(Keys.Home) def _(event): b = event.current_buffer b.cursor_position += b.document.get_start_of_line_position() @handle(Keys.End) def _(event): b = event.current_buffer b.cursor_position += b.document.get_end_of_line_position() # CTRL keys. @handle(Keys.ControlD, filter=Condition(lambda cli: cli.current_buffer.text)) def _(event): """ Delete text before cursor. """ event.current_buffer.delete(event.arg) @handle(Keys.ControlI, filter= ~has_selection) def _(event): r""" Ctrl-I is identical to "\t" Traditional tab-completion, where the first tab completes the common suffix and the second tab lists all the completions. """ b = event.current_buffer def second_tab(): if b.complete_state: b.complete_next() else: event.cli.start_completion(select_first=True) # On the second tab-press, or when already navigating through # completions. if event.is_repeat or b.complete_state: second_tab() else: event.cli.start_completion(insert_common_part=True) @handle(Keys.BackTab, filter= ~has_selection) def _(event): """ Shift+Tab: go to previous completion. """ event.current_buffer.complete_previous() @handle(Keys.ControlJ, filter= ~has_selection) def _(event): """ Newline/Enter. (Or return input.) """ b = event.current_buffer if b.is_multiline(): b.newline(copy_margin=not event.cli.in_paste_mode) else: if b.accept_action.is_returnable: b.accept_action.validate_and_handle(event.cli, b) @handle(Keys.ControlK, filter= ~has_selection) def _(event): buffer = event.current_buffer deleted = buffer.delete(count=buffer.document.get_end_of_line_position()) event.cli.clipboard.set_text(deleted) @handle(Keys.ControlT, filter= ~has_selection) def _(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() @handle(Keys.ControlU, filter= ~has_selection) def _(event): """ Clears the line before the cursor position. If you are at the end of the line, clears the entire line. """ buffer = event.current_buffer deleted = buffer.delete_before_cursor(count=-buffer.document.get_start_of_line_position()) event.cli.clipboard.set_text(deleted) @handle(Keys.ControlW, filter= ~has_selection) def _(event): """ Delete the word before the cursor. """ buffer = event.current_buffer pos = buffer.document.find_start_of_previous_word(count=event.arg) 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 = - buffer.cursor_position if pos: deleted = buffer.delete_before_cursor(count=-pos) # If the previous key press was also Control-W, concatenate deleted # text. if event.is_repeat: deleted += event.cli.clipboard.get_data().text event.cli.clipboard.set_text(deleted) @handle(Keys.PageUp, filter= ~has_selection) def _(event): event.current_buffer.history_backward() @handle(Keys.PageDown, filter= ~has_selection) def _(event): event.current_buffer.history_forward() @handle(Keys.Left) def _(event): buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_left_position(count=event.arg) @handle(Keys.Right) def _(event): buffer = event.current_buffer buffer.cursor_position += buffer.document.get_cursor_right_position(count=event.arg) @handle(Keys.Up, filter= ~has_selection) def _(event): event.current_buffer.auto_up(count=event.arg) @handle(Keys.Up, filter=has_selection) def _(event): event.current_buffer.cursor_up(count=event.arg) @handle(Keys.Down, filter= ~has_selection) def _(event): event.current_buffer.auto_down(count=event.arg) @handle(Keys.Down, filter=has_selection) def _(event): event.current_buffer.cursor_down(count=event.arg) @handle(Keys.ControlH, filter= ~has_selection, save_before=if_no_repeat) def _(event): " Backspace: delete before cursor. " event.current_buffer.delete_before_cursor(count=event.arg) @handle(Keys.Delete, filter= ~has_selection, save_before=if_no_repeat) @handle(Keys.ShiftDelete, filter= ~has_selection, save_before=if_no_repeat) def _(event): event.current_buffer.delete(count=event.arg) @handle(Keys.Delete, filter=has_selection) def _(event): data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(data) @handle(Keys.Any, filter= ~has_selection, save_before=if_no_repeat) def _(event): """ Insert data at cursor position. """ event.current_buffer.insert_text(event.data * event.arg) # Global bindings. These are never disabled and don't include the default filter. @handle(Keys.ControlL) def _(event): " Clear whole screen and redraw. " event.cli.renderer.clear() @handle(Keys.ControlZ) 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) @registry.add_binding(Keys.CPRResponse) 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.cli.renderer.report_absolute_cursor_row(row) @registry.add_binding(Keys.BracketedPaste) def _(event): " Pasting from clipboard. " event.current_buffer.insert_text(event.data) @registry.add_binding(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: MouseEventTypes.MOUSE_DOWN, 35: MouseEventTypes.MOUSE_UP, 96: MouseEventTypes.SCROLL_UP, 97: MouseEventTypes.SCROLL_DOWN, }.get(mouse_event) 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'): MouseEventTypes.MOUSE_DOWN, (0, 'm'): MouseEventTypes.MOUSE_UP, (64, 'M'): MouseEventTypes.SCROLL_UP, (65, 'M'): MouseEventTypes.SCROLL_DOWN, }.get((mouse_event, m)) else: mouse_event = { 32: MouseEventTypes.MOUSE_DOWN, 35: MouseEventTypes.MOUSE_UP, 96: MouseEventTypes.SCROLL_UP, 97: MouseEventTypes.SCROLL_DOWN, }.get(mouse_event) x -= 1 y -= 1 # Only handle mouse events when we know the window height. if event.cli.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.cli.renderer.rows_above_layout except HeightIsUnknownError: return # Call the mouse handler from the renderer. handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] handler(event.cli, MouseEvent(position=Point(x=x, y=y), event_type=mouse_event)) @registry.add_binding(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.cli.renderer.output.get_win32_screen_buffer_info() rows_above_cursor = screen_buffer_info.dwCursorPosition.Y - event.cli.renderer._cursor_pos.y y -= rows_above_cursor # Call the mouse event handler. handler = event.cli.renderer.mouse_handlers.mouse_handlers[x,y] handler(event.cli, MouseEvent(position=Point(x=x, y=y), event_type=event_type)) def load_abort_and_exit_bindings(registry, filter=Always()): """ Basic bindings for abort (Ctrl-C) and exit (Ctrl-D). """ assert isinstance(filter, CLIFilter) handle = create_handle_decorator(registry, filter) @handle(Keys.ControlC) def _(event): " Abort when Control-C has been pressed. " event.cli.abort() @Condition def ctrl_d_condition(cli): """ Ctrl-D binding is only active when the default buffer is selected and empty. """ return (cli.current_buffer_name == DEFAULT_BUFFER and not cli.current_buffer.text) @handle(Keys.ControlD, filter=ctrl_d_condition) def _(event): " Exit on Control-D when the input is empty. " event.cli.exit() def load_basic_system_bindings(registry, filter=Always()): """ Basic system bindings (For both Emacs and Vi mode.) """ assert isinstance(filter, CLIFilter) handle = create_handle_decorator(registry, filter) suspend_supported = Condition( lambda cli: suspend_to_background_supported()) @handle(Keys.ControlZ, filter=suspend_supported) def _(event): """ Suspend process to background. """ event.cli.suspend_to_background() def load_auto_suggestion_bindings(registry, filter=Always()): """ Key bindings for accepting auto suggestion text. """ assert isinstance(filter, CLIFilter) handle = create_handle_decorator(registry, filter) suggestion_available = Condition( lambda cli: cli.current_buffer.suggestion is not None and cli.current_buffer.document.is_cursor_at_the_end) @handle(Keys.ControlF, filter=suggestion_available) @handle(Keys.ControlE, filter=suggestion_available) @handle(Keys.Right, filter=suggestion_available) def _(event): " Accept suggestion. " b = event.current_buffer suggestion = b.suggestion if suggestion: b.insert_text(suggestion.text) prompt_toolkit-0.57/prompt_toolkit/key_binding/bindings/__init__.py0000644000175000000000000000000012556317626026606 0ustar jonathanroot00000000000000prompt_toolkit-0.57/prompt_toolkit/key_binding/bindings/scroll.py0000644000175000017500000001173212635143336027210 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 prompt_toolkit.layout.utils import find_window_for_buffer_name __all__ = ( 'scroll_forward', 'scroll_backward', 'scroll_half_page_up', 'scroll_half_page_down', 'scroll_one_line_up', 'scroll_one_line_down', ) def _current_window_for_event(event): """ Return the `Window` for the currently focussed Buffer. """ return find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) def scroll_forward(event, half=False): """ Scroll window down. """ w = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: # Determine height to move. shift = w.render_info.window_height if half: shift = int(shift / 2) # Scroll. new_document_line = min( b.document.line_count - 1, b.document.cursor_position_row + int(shift)) b.cursor_position = b.document.translate_row_col_to_index(new_document_line, 0) w.vertical_scroll = w.render_info.input_line_to_screen_line[new_document_line] def scroll_backward(event, half=False): """ Scroll window up. """ w = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: # Determine height to move. shift = w.render_info.window_height if half: shift = int(shift / 2) # Scroll. new_document_line = max(0, b.document.cursor_position_row - int(shift)) b.cursor_position = b.document.translate_row_col_to_index(new_document_line, 0) w.vertical_scroll = w.render_info.input_line_to_screen_line[new_document_line] 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 = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.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 = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) b = event.cli.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: if info.cursor_position.y >= info.window_height - 1 - info.configured_scroll_offsets.bottom: 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 = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: # Scroll down one page. w.vertical_scroll += w.render_info.window_height # Put cursor at the top of the visible region. try: new_document_line = w.render_info.screen_line_to_input_line[w.vertical_scroll] except KeyError: new_document_line = b.document.line_count - 1 b.cursor_position = b.document.translate_row_col_to_index(new_document_line, 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 = _current_window_for_event(event) b = event.cli.current_buffer if w and w.render_info: # Scroll down one page. w.vertical_scroll = max(0, w.vertical_scroll - w.render_info.window_height) # Put cursor at the bottom of the visible region. try: new_document_line = w.render_info.screen_line_to_input_line[ w.vertical_scroll + w.render_info.window_height - 1] except KeyError: new_document_line = 0 b.cursor_position = min(b.cursor_position, b.document.translate_row_col_to_index(new_document_line, 0)) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) prompt_toolkit-0.57/prompt_toolkit/key_binding/input_processor.py0000644000175000017500000002135612610321204027337 0ustar jonathanjonathan00000000000000# *** encoding: utf-8 *** """ An :class:`~.InputProcessor` receives callbacks for the keystrokes parsed from the input in the :class:`~prompt_toolkit.inputstream.InputStream` instance. The `InputProcessor` will according to the implemented keybindings call the correct callbacks when new key presses are feed through `feed_key`. """ from __future__ import unicode_literals from ..keys import Keys from ..utils import Callback from prompt_toolkit.buffer import EditReadOnlyBuffer import weakref __all__ = ( 'InputProcessor', 'KeyPress', ) class KeyPress(object): """ :param key: a `Keys` instance. :param data: The received string on stdin. (Often vt100 escape codes.) """ def __init__(self, key, data): 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 class InputProcessor(object): """ Statemachine that receives :class:`KeyPress` instances and according to the key bindings in the given :class:`Registry`, calls the matching handlers. :: p = InputProcessor(registry) # Send keys into the processor. p.feed_key(KeyPress(Keys.ControlX, '\x18')) p.feed_key(KeyPress(Keys.ControlC, '\x03') # Now the ControlX-ControlC callback will be called if this sequence is # registered in the registry. :param registry: `Registry` instance. :param cli_ref: weakref to `CommandLineInterface`. """ def __init__(self, registry, cli_ref): self._registry = registry self._cli_ref = cli_ref self.reset() self.beforeKeyPress = Callback() self.afterKeyPress = Callback() # print(' '.join(set(''.join(map(str, kb.keys)) for kb in registry.key_bindings if all(isinstance(X, unicode) for X in kb.keys)))) def reset(self): self._previous_key_sequence = None self._previous_handler = None self._process_coroutine = self._process() self._process_coroutine.send(None) #: Readline argument (for repetition of commands.) #: https://www.gnu.org/software/bash/manual/html_node/Readline-Arguments.html self.arg = 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) cli = self._cli_ref() # Try match, with mode flag with_mode = [b for b in self._registry.get_bindings_for_keys(keys) if b.filter(cli)] if with_mode: return with_mode # Try match, where the last key is replaced with 'Any', with mode. keys_any = tuple(keys[:-1] + (Keys.Any,)) with_mode_any = [b for b in self._registry.get_bindings_for_keys(keys_any) if b.filter(cli)] if with_mode_any: return with_mode_any return [] 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) cli = self._cli_ref() # 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._registry.get_bindings_starting_with_keys(keys)) # When any key binding is active, return True. return any(f(cli) 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 = [] retry = False while True: if retry: retry = False else: buffer.append((yield)) # If we have some key presses, check for matches. if buffer: is_prefix_of_longer_match = self._is_prefix_of_longer_match(buffer) matches = self._get_matches(buffer) # When longer matches were found, but the current match is # 'eager', ignore all the longer matches. if matches and matches[-1].eager(self._cli_ref()): 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) buffer = [] # 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]) buffer = buffer[i:] found = True if not found: buffer = buffer[1:] def feed_key(self, key_press): """ Send a new :class:`KeyPress` into this processor. """ assert isinstance(key_press, KeyPress) if key_press.key != Keys.CPRResponse: self.beforeKeyPress.fire() self._process_coroutine.send(key_press) if key_press.key != Keys.CPRResponse: self.afterKeyPress.fire() def _call_handler(self, handler, key_sequence=None): arg = self.arg self.arg = None is_repeat = handler == self._previous_handler try: event = Event(weakref.ref(self), arg=arg, key_sequence=key_sequence, previous_key_sequence=self._previous_key_sequence, is_repeat=is_repeat) handler.call(event) self._registry.on_handler_called.fire(event) except EditReadOnlyBuffer: # When a key binding does an attempt to change a buffer which is read-only, # we can just silently ignore that. pass self._previous_key_sequence = key_sequence self._previous_handler = handler # Invalidate user interface. cli = self._cli_ref() if cli and handler.invalidate_ui(cli): cli.invalidate() class Event(object): """ Key press event, delivered to key bindings. :param input_processor_ref: Weak reference to the `InputProcessor`. :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, input_processor_ref, arg=None, key_sequence=None, previous_key_sequence=None, is_repeat=False): self._input_processor_ref = input_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 def __repr__(self): return 'Event(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 input_processor(self): return self._input_processor_ref() @property def cli(self): """ Command line interface. """ return self.input_processor._cli_ref() @property def current_buffer(self): """ The current buffer. """ return self.cli.current_buffer @property def arg(self): """ Repetition argument. """ return self._arg or 1 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 current is None: if data == '-': data = '-1' result = int(data) else: result = int("%s%s" % (current, data)) # Don't exceed a million. if int(result) >= 1000000: result = None self.input_processor.arg = result prompt_toolkit-0.57/prompt_toolkit/key_binding/manager.py0000644000175000017500000001473712636171640025537 0ustar jonathanjonathan00000000000000""" :class:`KeyBindingManager` is a utility (or shortcut) for loading all the key bindings in a key binding registry, with a logic set of filters to quickly to quickly change from Vi to Emacs key bindings at runtime. You don't have to use this, but it's practical. Usage:: manager = KeyBindingManager() cli = CommandLineInterface(key_bindings_registry=manager.registry) """ from __future__ import unicode_literals from prompt_toolkit.key_binding.registry import Registry from prompt_toolkit.key_binding.vi_state import ViState from prompt_toolkit.key_binding.bindings.basic import load_basic_bindings, load_abort_and_exit_bindings, load_basic_system_bindings, load_auto_suggestion_bindings from prompt_toolkit.key_binding.bindings.emacs import load_emacs_bindings, load_emacs_system_bindings, load_emacs_search_bindings, load_emacs_open_in_editor_bindings, load_extra_emacs_page_navigation_bindings from prompt_toolkit.key_binding.bindings.vi import load_vi_bindings, load_vi_system_bindings, load_vi_search_bindings, load_vi_open_in_editor_bindings, load_extra_vi_page_navigation_bindings from prompt_toolkit.filters import to_cli_filter __all__ = ( 'KeyBindingManager', ) class KeyBindingManager(object): """ Utility for loading all key bindings into memory. :param registry: Optional `Registry` instance. :param enable_vi_mode: Filter to enable Vi-mode. :param enable_abort_and_exit_bindings: Filter to enable Ctrl-C and Ctrl-D. :param enable_system_bindings: Filter to enable the system bindings (meta-! prompt and Control-Z suspension.) :param enable_search: Filter to enable the search bindings. :param enable_open_in_editor: Filter to enable open-in-editor. :param enable_open_in_editor: Filter to enable open-in-editor. :param enable_extra_page_navigation: Filter for enabling extra page navigation. (Bindings for up/down scrolling through long pages, like in Emacs or Vi.) :param enable_auto_suggest_bindings: Filter to enable fish-style suggestions. :param enable_all: Filter to enable (or disable) all bindings. """ def __init__(self, registry=None, enable_vi_mode=False, get_vi_state=None, get_search_state=None, enable_abort_and_exit_bindings=False, enable_system_bindings=False, enable_search=False, enable_open_in_editor=False, enable_extra_page_navigation=False, enable_auto_suggest_bindings=False, enable_all=True): assert registry is None or isinstance(registry, Registry) assert get_vi_state is None or callable(get_vi_state) assert get_search_state is None or callable(get_search_state) # Create registry. self.registry = registry or Registry() # Vi state. (Object to keep track of in which Vi mode we are.) if get_vi_state is None: vi_state = ViState() # Stateful. Should be defined outside the function below. def get_vi_state(cli): return vi_state self.get_vi_state = get_vi_state # Accept both Filters and booleans as input. enable_vi_mode = to_cli_filter(enable_vi_mode) enable_abort_and_exit_bindings = to_cli_filter(enable_abort_and_exit_bindings) enable_system_bindings = to_cli_filter(enable_system_bindings) enable_search = to_cli_filter(enable_search) enable_open_in_editor = to_cli_filter(enable_open_in_editor) enable_extra_page_navigation = to_cli_filter(enable_extra_page_navigation) enable_auto_suggest_bindings = to_cli_filter(enable_auto_suggest_bindings) enable_all = to_cli_filter(enable_all) # Emacs mode filter is the opposite of Vi mode. enable_emacs_mode = ~enable_vi_mode # Load basic bindings. load_basic_bindings(self.registry, enable_all) load_abort_and_exit_bindings( self.registry, enable_abort_and_exit_bindings & enable_all) load_basic_system_bindings(self.registry, enable_system_bindings & enable_all) # Load emacs bindings. load_emacs_bindings(self.registry, enable_emacs_mode & enable_all) load_emacs_open_in_editor_bindings( self.registry, enable_emacs_mode & enable_open_in_editor & enable_all) load_emacs_search_bindings( self.registry, filter=enable_emacs_mode & enable_search & enable_all, get_search_state=get_search_state) load_emacs_system_bindings( self.registry, enable_emacs_mode & enable_system_bindings & enable_all) load_extra_emacs_page_navigation_bindings( self.registry, enable_emacs_mode & enable_extra_page_navigation & enable_all) # Load Vi bindings. load_vi_bindings( self.registry, self.get_vi_state, enable_visual_key=~enable_open_in_editor, filter=enable_vi_mode & enable_all, get_search_state=get_search_state) load_vi_open_in_editor_bindings( self.registry, self.get_vi_state, enable_vi_mode & enable_open_in_editor & enable_all) load_vi_search_bindings( self.registry, self.get_vi_state, filter=enable_vi_mode & enable_search & enable_all, get_search_state=get_search_state) load_vi_system_bindings( self.registry, self.get_vi_state, enable_vi_mode & enable_system_bindings & enable_all) load_extra_vi_page_navigation_bindings( self.registry, enable_vi_mode & enable_extra_page_navigation & enable_all) # Suggestion bindings. # (This has to come at the end, because the Vi bindings also have an # implementation for the "right arrow", but we really want the # suggestion binding when a suggestion is available.) load_auto_suggestion_bindings( self.registry, enable_auto_suggest_bindings & enable_all) @classmethod def for_prompt(cls, **kw): """ Create a ``KeyBindingManager`` with the defaults for an input prompt. This activates incremental the bindings for abort/exit (Ctrl-C/Ctrl-D), incremental search and auto suggestions. (Not for full screen applications.) """ kw.setdefault('enable_abort_and_exit_bindings', True) kw.setdefault('enable_search', True) kw.setdefault('enable_auto_suggest_bindings', True) return cls(**kw) def reset(self, cli): self.get_vi_state(cli).reset() prompt_toolkit-0.57/prompt_toolkit/key_binding/__init__.py0000644000175000000000000000005012556317626025016 0ustar jonathanroot00000000000000from __future__ import unicode_literals prompt_toolkit-0.57/prompt_toolkit/auto_suggest.py0000644000175000017500000000543212623240275024341 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. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from .filters import to_cli_filter __all__ = ( 'Suggestion', 'AutoSuggest', 'AutoSuggestFromHistory', 'ConditionalAutoSuggest', ) 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, cli, buffer, document): """ Return `None` or a :class:`.Suggestion` instance. We receive both ``buffer`` and ``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. """ class AutoSuggestFromHistory(AutoSuggest): """ Give suggestions based on the lines in the history. """ def get_suggestion(self, cli, 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)): 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_cli_filter(filter) def get_suggestion(self, cli, buffer, document): if self.filter(cli): return self.auto_suggest.get_suggestion(cli, buffer, document) prompt_toolkit-0.57/prompt_toolkit/terminal/0000755000175000017500000000000012642647210023066 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/terminal/vt100_input.py0000644000175000017500000003362412635170560025542 0ustar jonathanjonathan00000000000000""" Parser for VT100 input stream. """ from __future__ import unicode_literals import os import re import six import termios import tty from ..keys import Keys from ..key_binding.input_processor import KeyPress __all__ = ( 'InputStream', 'raw_mode', 'cooked_mode', ) _DEBUG_RENDERER_INPUT = False _DEBUG_RENDERER_INPUT_FILENAME = 'prompt-toolkit-render-input.log' # 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'(: 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 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 = windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE) self._in_alternate_screen = False self.color_lookup_table = ColorLookupTable() if _DEBUG_RENDER_OUTPUT: self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, 'ab') def write(self, 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. """ sbinfo = CONSOLE_SCREEN_BUFFER_INFO() success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo, self.hconsole, byref(sbinfo)) if success: return sbinfo 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): self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, 15) # White def set_attributes(self, attrs): fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs if reverse: fgcolor, bgcolor = bgcolor, fgcolor i = self.color_lookup_table.lookup_color(fgcolor, bgcolor) self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, i) 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 soluton # 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 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 = 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 = 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 = 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 = 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 = 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 BACKROUND_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 { 'black': color_cls.BLACK, 'default': color_cls.BLACK, 'white': color_cls.GRAY | color_cls.INTENSITY, # Low intensity. 'red': color_cls.RED, 'green': color_cls.GREEN, 'yellow': color_cls.YELLOW, 'blue': color_cls.BLUE, 'magenta': color_cls.MAGENTA, 'cyan': color_cls.CYAN, 'gray': color_cls.GRAY, # High intensity. 'dark-gray': color_cls.BLACK | color_cls.INTENSITY, 'bright-red': color_cls.RED | color_cls.INTENSITY, 'bright-green': color_cls.GREEN | color_cls.INTENSITY, 'bright-yellow': color_cls.YELLOW | color_cls.INTENSITY, 'bright-blue': color_cls.BLUE | color_cls.INTENSITY, 'bright-magenta': color_cls.MAGENTA | color_cls.INTENSITY, 'bright-cyan': color_cls.CYAN | color_cls.INTENSITY, } FG_ANSI_COLORS = _create_ansi_color_dict(FOREGROUND_COLOR) BG_ANSI_COLORS = _create_ansi_color_dict(BACKROUND_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 = BACKROUND_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), (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_color(self, fg_color, bg_color): """ Return the color for use in the `windll.kernel32.SetConsoleTextAttribute` API call. :param fg_color: Foreground as text. E.g. 'ffffff' or 'red' :param bg_color: Background as text. E.g. 'ffffff' or 'red' """ # Take white as the default foreground color. # (otherwise many things will be invisible.) if fg_color is None: fg_color = 'ffffff' # Foreground. if fg_color in FG_ANSI_COLORS: fg_index = FG_ANSI_COLORS[fg_color] else: fg_index = self._color_indexes(fg_color)[0] # Background. if bg_color in BG_ANSI_COLORS: bg_index = BG_ANSI_COLORS[bg_color] else: bg_index = self._color_indexes(bg_color)[1] return fg_index | bg_index prompt_toolkit-0.57/prompt_toolkit/terminal/__init__.py0000644000175000000000000000000012556317626024342 0ustar jonathanroot00000000000000prompt_toolkit-0.57/prompt_toolkit/terminal/vt100_output.py0000644000175000017500000002521412642143756025744 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from pygments.formatters.terminal256 import Terminal256Formatter from prompt_toolkit.filters import to_simple_filter from prompt_toolkit.layout.screen import Size from prompt_toolkit.renderer import Output from prompt_toolkit.styles import ANSI_COLOR_NAMES import array import errno import six __all__ = ( 'Vt100_Output', ) # Global variable to keep the colour table in memory. _tf = Terminal256Formatter() #: 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 = '/tmp/prompt-toolkit-render-output' FG_ANSI_COLORS = { 'black': 30, 'default': 39, 'white': 97, # Low intensity. 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'gray': 37, # High intensity. 'dark-gray': 90, # Bright black. 'bright-red': 91, 'bright-green': 92, 'bright-yellow': 93, 'bright-blue': 94, 'bright-magenta': 95, 'bright-cyan': 96, } BG_ANSI_COLORS = { 'black': 40, 'default': 49, 'white': 107, # Low intensity. 'red': 41, 'green': 42, 'yellow': 43, 'blue': 44, 'magenta': 45, 'cyan': 46, 'gray': 47, # High intensity. 'dark-gray': 100, # bright black. 'bright-red': 101, 'bright-green': 102, 'bright-yellow': 103, 'bright-blue': 104, 'bright-magenta': 105, 'bright-cyan': 106, } assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) 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, true_color=False): assert isinstance(true_color, bool) self.true_color = true_color def __missing__(self, attrs): fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs parts = [] if fgcolor: parts.extend(self._color_to_code(fgcolor)) if bgcolor: parts.extend(self._color_to_code(bgcolor, True)) 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 parts: result = '\x1b[0;' + ';'.join(parts) + 'm' else: result = '\x1b[0m' self[attrs] = result return result def _color_to_code(self, color, bg=False): table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS # 16 ANSI colors. if color in table: result = (table[color], ) # True colors. elif self.true_color: try: rgb = int(color, 16) except ValueError: result = [] r = (rgb >> 16) & 0xff g = (rgb >> 8) & 0xff b = rgb & 0xff result = (48 if bg else 38, 2, r, g, b) # 256 RGB colors. else: result = (48 if bg else 38, 5, _tf._color_index(color)) return map(six.text_type, result) _ESCAPE_CODE_CACHE = _EscapeCodeCache(true_color=False) _ESCAPE_CODE_CACHE_TRUE_COLOR = _EscapeCodeCache(true_color=True) 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(u'h' if six.PY3 else b'h', [0, 0, 0, 0]) # Do TIOCGWINSZ (Get) fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf, True) # 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. :param true_color: Use 24bit color instead of 256 colors. (Can be a :class:`SimpleFilter`.) """ def __init__(self, stdout, get_size, true_color=False): self._buffer = [] self.stdout = stdout self.get_size = get_size self.true_color = to_simple_filter(true_color) @classmethod def from_pty(cls, stdout, true_color=False): """ Create an Output class from a pseudo terminal. (This will take the dimensions by reading the pseudo terminal attributes.) """ def get_size(): rows, columns = _get_size(stdout.fileno()) return Size(rows=rows, columns=columns) return cls(stdout, get_size, true_color=true_color) 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. """ 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): """ Create new style and output. :param attrs: `Attrs` instance. """ if self.true_color(): self.write_raw(_ESCAPE_CODE_CACHE_TRUE_COLOR[attrs]) else: 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: self.write_raw('') elif amount == 1: self.write_raw('\x1b[A') else: self.write_raw('\x1b[%iA' % amount) def cursor_down(self, amount): if amount == 0: self.write_raw('') 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: self.write_raw('') elif amount == 1: self.write_raw('\x1b[C') else: self.write_raw('\x1b[%iC' % amount) def cursor_backward(self, amount): if amount == 0: self.write_raw('') 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 hasattr(self.stdout, 'encoding'): out = self.stdout.buffer if six.PY3 else 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 happpen 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-0.57/prompt_toolkit/eventloop/0000755000175000017500000000000012642647210023266 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/eventloop/utils.py0000644000175000017500000000076112556570471025014 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import time __all__ = ( 'TimeIt', ) class TimeIt(object): """ Context manager that times the duration of the code body. The `duration` attribute will contain the execution time in seconds. """ def __init__(self): self.duration = None def __enter__(self): self.start = time.time() return self def __exit__(self, *args): self.end = time.time() self.duration = self.end - self.start prompt_toolkit-0.57/prompt_toolkit/eventloop/asyncio_posix.py0000644000175000017500000000620512621515730026530 0ustar jonathanjonathan00000000000000""" Posix asyncio event loop. """ from __future__ import unicode_literals from ..terminal.vt100_input import InputStream from .asyncio_base import AsyncioTimeout from .base import EventLoop, INPUT_TIMEOUT from .callbacks import EventLoopCallbacks from .posix_utils import PosixStdinReader import asyncio import signal __all__ = ( 'PosixAsyncioEventLoop', ) class PosixAsyncioEventLoop(EventLoop): def __init__(self, loop=None): self.loop = loop or asyncio.get_event_loop() self.closed = False self._stopped_f = asyncio.Future() @asyncio.coroutine def run_as_coroutine(self, stdin, callbacks): """ The input 'event loop'. """ assert isinstance(callbacks, EventLoopCallbacks) # Create reader class. stdin_reader = PosixStdinReader(stdin.fileno()) if self.closed: raise Exception('Event loop already closed.') inputstream = InputStream(callbacks.feed_key) try: # Create a new Future every time. self._stopped_f = asyncio.Future() # Handle input timouts def timeout_handler(): """ When no input has been received for INPUT_TIMEOUT seconds, flush the input stream and fire the timeout event. """ inputstream.flush() callbacks.input_timeout() timeout = AsyncioTimeout(INPUT_TIMEOUT, timeout_handler, self.loop) # Catch sigwinch def received_winch(): self.call_from_executor(callbacks.terminal_size_changed) self.loop.add_signal_handler(signal.SIGWINCH, received_winch) # Read input data. def stdin_ready(): data = stdin_reader.read() inputstream.feed(data) timeout.reset() self.loop.add_reader(stdin.fileno(), stdin_ready) # Block this coroutine until stop() has been called. for f in self._stopped_f: yield f finally: # Clean up. self.loop.remove_reader(stdin.fileno()) self.loop.remove_signal_handler(signal.SIGWINCH) # Don't trigger any timeout events anymore. timeout.stop() def stop(self): # Trigger the 'Stop' future. self._stopped_f.set_result(True) def close(self): # Note: we should not close the asyncio loop itself, because that one # was not created here. self.closed = True def run_in_executor(self, callback): self.loop.run_in_executor(None, callback) def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. """ self.loop.call_soon_threadsafe(callback) def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " self.loop.add_reader(fd, callback) def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " self.loop.remove_reader(fd) prompt_toolkit-0.57/prompt_toolkit/eventloop/asyncio_win32.py0000644000175000017500000000451712621515730026334 0ustar jonathanjonathan00000000000000""" Win32 asyncio event loop. Windows notes: - Somehow it doesn't seem to work with the 'ProactorEventLoop'. """ from __future__ import unicode_literals from .base import EventLoop, INPUT_TIMEOUT from ..terminal.win32_input import ConsoleInputReader from .callbacks import EventLoopCallbacks from .asyncio_base import AsyncioTimeout import asyncio __all__ = ( 'Win32AsyncioEventLoop', ) class Win32AsyncioEventLoop(EventLoop): def __init__(self, loop=None): self._console_input_reader = ConsoleInputReader() self.running = False self.closed = False self.loop = loop or asyncio.get_event_loop() @asyncio.coroutine def run_as_coroutine(self, stdin, callbacks): """ The input 'event loop'. """ # Note: We cannot use "yield from", because this package also # installs on Python 2. assert isinstance(callbacks, EventLoopCallbacks) if self.closed: raise Exception('Event loop already closed.') timeout = AsyncioTimeout(INPUT_TIMEOUT, callbacks.input_timeout, self.loop) self.running = True try: while self.running: timeout.reset() # Get keys try: g = iter(self.loop.run_in_executor(None, self._console_input_reader.read)) while True: yield next(g) except StopIteration as e: keys = e.args[0] # Feed keys to input processor. for k in keys: callbacks.feed_key(k) finally: timeout.stop() def stop(self): self.running = 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_in_executor(self, callback): self.loop.run_in_executor(None, callback) def call_from_executor(self, callback, _max_postpone_until=None): self.loop.call_soon_threadsafe(callback) def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " self.loop.add_reader(fd, callback) def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " self.loop.remove_reader(fd) prompt_toolkit-0.57/prompt_toolkit/eventloop/win32.py0000644000175000017500000001276212621515730024610 0ustar jonathanjonathan00000000000000""" Win32 event loop. Windows notes: - Somehow it doesn't seem to work with the 'ProactorEventLoop'. """ from __future__ import unicode_literals from ..terminal.win32_input import ConsoleInputReader from ..win32_types import SECURITY_ATTRIBUTES from .base import EventLoop, INPUT_TIMEOUT from .inputhook import InputHookContext from .utils import TimeIt from ctypes import windll, pointer from ctypes.wintypes import DWORD, BOOL, HANDLE import threading __all__ = ( 'Win32EventLoop', ) WAIT_TIMEOUT = 0x00000102 INPUT_TIMEOUT_MS = int(1000 * INPUT_TIMEOUT) class Win32EventLoop(EventLoop): """ Event loop for Windows systems. """ def __init__(self, inputhook=None): assert inputhook is None or callable(inputhook) self._event = _create_event() self._console_input_reader = ConsoleInputReader() self._calls_from_executor = [] self.closed = False self._running = False # Create inputhook context. self._inputhook_context = InputHookContext(inputhook) if inputhook else None def run(self, stdin, callbacks): if self.closed: raise Exception('Event loop already closed.') current_timeout = INPUT_TIMEOUT_MS self._running = True while self._running: # Call inputhook. with TimeIt() as inputhook_timer: if self._inputhook_context: def ready(wait): " True when there is input ready. The inputhook should return control. " return bool(self._ready_for_reading(current_timeout if wait else 0)) self._inputhook_context.call_inputhook(ready) # Calculate remaining timeout. (The inputhook consumed some of the time.) if current_timeout == -1: remaining_timeout = -1 else: remaining_timeout = max(0, current_timeout - int(1000 * inputhook_timer.duration)) # Wait for the next event. handle = self._ready_for_reading(remaining_timeout) if handle == self._console_input_reader.handle: # When stdin is ready, read input and reset timeout timer. keys = self._console_input_reader.read() for k in keys: callbacks.feed_key(k) current_timeout = INPUT_TIMEOUT_MS elif handle == self._event: # When the Windows Event has been trigger, process the messages in the queue. windll.kernel32.ResetEvent(self._event) self._process_queued_calls_from_executor() else: # Fire input timeout event. callbacks.input_timeout() current_timeout = -1 def _ready_for_reading(self, timeout=None): """ Return the handle that is ready for reading or `None` on timeout. """ return _wait_for_handles([self._event, self._console_input_reader.handle], timeout) def stop(self): self._running = False 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): """ 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``. """ # 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`.) def start_executor(): threading.Thread(target=callback).start() self.call_from_executor(start_executor) def call_from_executor(self, callback, _max_postpone_until=None): """ Call this function in the main event loop. Similar to Twisted's ``callFromThread``. """ # 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 = self._calls_from_executor, [] for c in calls_from_executor: c() def add_reader(self, fd, callback): " Start watching the file descriptor for read availability. " raise NotImplementedError def remove_reader(self, fd): " Stop watching the file descriptor for read availability. " raise NotImplementedError def _wait_for_handles(handles, timeout=-1): """ 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 """ 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 h def _create_event(): """ Creates a Win32 unnamed Event . http://msdn.microsoft.com/en-us/library/windows/desktop/ms682396(v=vs.85).aspx """ return windll.kernel32.CreateEventA(pointer(SECURITY_ATTRIBUTES()), BOOL(True), BOOL(False), None) prompt_toolkit-0.57/prompt_toolkit/eventloop/posix.py0000644000175000017500000002611512630722602025003 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals import datetime import errno import fcntl import os import random import select import signal import threading from prompt_toolkit.terminal.vt100_input import InputStream from prompt_toolkit.utils import DummyContext, in_main_thread from prompt_toolkit.input import Input from .base import EventLoop, INPUT_TIMEOUT from .callbacks import EventLoopCallbacks from .inputhook import InputHookContext from .posix_utils import PosixStdinReader from .utils import TimeIt __all__ = ( 'PosixEventLoop', ) _now = datetime.datetime.now class PosixEventLoop(EventLoop): """ Event loop for posix systems (Linux, Mac os X). """ def __init__(self, inputhook=None): assert inputhook is None or callable(inputhook) self.running = False self.closed = False self._running = False self._callbacks = None self._calls_from_executor = [] self._read_fds = {} # Maps fd to 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) # Create inputhook context. self._inputhook_context = InputHookContext(inputhook) if inputhook else None def run(self, stdin, callbacks): """ The input 'event loop'. """ assert isinstance(stdin, Input) assert isinstance(callbacks, EventLoopCallbacks) assert not self._running if self.closed: raise Exception('Event loop already closed.') self._running = True self._callbacks = callbacks inputstream = InputStream(callbacks.feed_key) current_timeout = [INPUT_TIMEOUT] # Nonlocal # Create reader class. stdin_reader = PosixStdinReader(stdin.fileno()) # Only attach SIGWINCH signal handler in main thread. # (It's not possible to attach signal handlers in other threads. In # that case we should rely on a the main thread to call this manually # instead.) if in_main_thread(): ctx = call_on_sigwinch(self.received_winch) else: ctx = DummyContext() def read_from_stdin(): " Read user input. " # Feed input text. data = stdin_reader.read() inputstream.feed(data) # Set timeout again. current_timeout[0] = INPUT_TIMEOUT self.add_reader(stdin, read_from_stdin) self.add_reader(self._schedule_pipe[0], None) with ctx: while self._running: # Call inputhook. with TimeIt() as inputhook_timer: if self._inputhook_context: def ready(wait): " True when there is input ready. The inputhook should return control. " return self._ready_for_reading(current_timeout[0] if wait else 0) != [] self._inputhook_context.call_inputhook(ready) # Calculate remaining timeout. (The inputhook consumed some of the time.) if current_timeout[0] is None: remaining_timeout = None else: remaining_timeout = max(0, current_timeout[0] - inputhook_timer.duration) # Wait until input is ready. fds = self._ready_for_reading(remaining_timeout) # 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 = _now() 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]: for c, max_postpone_until in self._calls_from_executor: if max_postpone_until is None or max_postpone_until < now: tasks.append(c) else: low_priority_tasks.append((c, max_postpone_until)) self._calls_from_executor = [] # Flush all the pipe content. os.read(self._schedule_pipe[0], 1024) else: handler = self._read_fds.get(fd) if handler: tasks.append(handler) # Handle everything in random order. (To avoid starvation.) random.shuffle(tasks) random.shuffle(low_priority_tasks) # When there are high priority tasks, run all these. # Schedule low priority tasks for the next iteration. if tasks: for t in tasks: 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: t() else: # Flush all pending keys on a timeout. (This is most # important to flush the vt100 'Escape' key early when # nothing else follows.) inputstream.flush() # Fire input timeout event. callbacks.input_timeout() current_timeout[0] = None self.remove_reader(stdin) self.remove_reader(self._schedule_pipe[0]) self._callbacks = None def _ready_for_reading(self, timeout=None): """ Return the file descriptors that are ready for reading. """ read_fds = list(self._read_fds.keys()) r, _, _ =_select(read_fds, [], [], timeout) return r def received_winch(self): """ Notify the event loop that SIGWINCH has been received """ # Process signal asynchronously, because this handler can write to the # output, and doing this inside the signal handler causes easily # reentrant calls, giving runtime errors.. # Furthur, this has to be thread safe. When the CommandLineInterface # runs not in the main thread, this function still has to be called # from the main thread. (The only place where we can install signal # handlers.) def process_winch(): if self._callbacks: self._callbacks.terminal_size_changed() self.call_from_executor(process_winch) def run_in_executor(self, callback): """ 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``. """ # 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 noticable when pasting large portions of text while # having real time autocompletion while typing on. def start_executor(): threading.Thread(target=callback).start() self.call_from_executor(start_executor) 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 `datetime` instance. For interal 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.) """ self._calls_from_executor.append((callback, _max_postpone_until)) if self._schedule_pipe: os.write(self._schedule_pipe[1], b'x') def stop(self): """ Stop the event loop. """ self._running = False def close(self): 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. " self._read_fds[fd] = callback def remove_reader(self, fd): " Remove read file descriptor from the event loop. " if fd in self._read_fds: del self._read_fds[fd] def _select(*args, **kwargs): """ 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. """ while True: try: return select.select(*args, **kwargs) except select.error as e: # Retry select call when EINTR if e.args and e.args[0] == errno.EINTR: continue else: raise class call_on_sigwinch(object): """ Context manager which Installs a SIGWINCH callback. (This signal occurs when the terminal size changes.) """ def __init__(self, callback): self.callback = callback self.previous_callback = None def __enter__(self): self.previous_callback = signal.signal(signal.SIGWINCH, lambda *a: self.callback()) def __exit__(self, *a, **kw): if self.previous_callback is None: # Normally, `signal.signal` should never return `None`. # For some reason it happens here: # https://github.com/jonathanslenders/python-prompt-toolkit/pull/174 signal.signal(signal.SIGWINCH, 0) else: signal.signal(signal.SIGWINCH, self.previous_callback) prompt_toolkit-0.57/prompt_toolkit/eventloop/inputhook.py0000644000175000017500000000532512556570471025675 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 __all__ = ( 'InputHookContext', ) class InputHookContext(object): """ Given as a parameter to the inputhook. """ def __init__(self, inputhook): assert callable(inputhook) self.inputhook = inputhook 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): """ Call the inputhook. (Called by a prompt-toolkit eventloop.) """ 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. self.inputhook(self) # Flush the read end of the pipe. try: 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-0.57/prompt_toolkit/eventloop/asyncio_base.py0000644000175000000000000000220012556317626025446 0ustar jonathanroot00000000000000""" Eventloop for integration with Python3 asyncio. Note that we can't use "yield from", because the package should be installable under Python 2.6 as well, and it should contain syntactically valid Python 2.6 code. """ from __future__ import unicode_literals __all__ = ( 'AsyncioTimeout', ) class AsyncioTimeout(object): """ Call the `timeout` function when the timeout expires. Every call of the `reset` method, resets the timeout and starts a new timer. """ def __init__(self, timeout, callback, loop): self.timeout = timeout self.callback = callback self.loop = loop self.counter = 0 self.running = True def reset(self): """ Reset the timeout. Starts a new timer. """ self.counter += 1 local_counter = self.counter def timer_timeout(): if self.counter == local_counter and self.running: self.callback() self.loop.call_later(self.timeout, timer_timeout) def stop(self): """ Ignore timeout. Don't call the callback anymore. """ self.running = False prompt_toolkit-0.57/prompt_toolkit/eventloop/posix_utils.py0000644000175000017500000000374112623235713026227 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from codecs import getincrementaldecoder import os __all__ = ( 'PosixStdinReader', ) class PosixStdinReader(object): """ Wrapper around stdin which reads (nonblocking) the next available 1024 bytes and decodes it. """ def __init__(self, stdin_fd): assert isinstance(stdin_fd, int) self.stdin_fd = stdin_fd # 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() 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. """ # 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) except OSError: # In case of SIGWINCH data = b'' try: return self._stdin_decoder.decode(data) except UnicodeDecodeError: # When it's not possible to decode this bytes, reset the decoder. # The only occurence of this that I had was when using iTerm2 on OS # X, with "Option as Meta" checked (You should choose "Option as # +Esc".) self._stdin_decoder = self._stdin_decoder_cls() return '' prompt_toolkit-0.57/prompt_toolkit/eventloop/callbacks.py0000644000175000017500000000133712610321204025546 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'EventLoopCallbacks', ) class EventLoopCallbacks(with_metaclass(ABCMeta, object)): """ This is the glue between the :class:`~prompt_toolkit.eventloop.base.EventLoop` and :class:`~prompt_toolkit.interface.CommandLineInterface`. :meth:`~prompt_toolkit.eventloop.base.EventLoop.run` takes an :class:`.EventLoopCallbacks` instance and operates on that one, driving the interface. """ @abstractmethod def terminal_size_changed(self): pass @abstractmethod def input_timeout(self): pass @abstractmethod def feed_key(self, key): pass prompt_toolkit-0.57/prompt_toolkit/eventloop/__init__.py0000644000175000000000000000000012556317626024542 0ustar jonathanroot00000000000000prompt_toolkit-0.57/prompt_toolkit/eventloop/base.py0000644000175000017500000000473112621515730024555 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'EventLoop', 'INPUT_TIMEOUT', ) #: When to trigger the `onInputTimeout` event. INPUT_TIMEOUT = .5 class EventLoop(with_metaclass(ABCMeta, object)): """ Eventloop interface. """ def run(self, stdin, callbacks): """ Run the eventloop until stop() is called. Report all input/timeout/terminal-resize events to the callbacks. :param stdin: :class:`~prompt_toolkit.input.Input` instance. :param callbacks: :class:`~prompt_toolkit.eventloop.callbacks.EventLoopCallbacks` instance. """ raise NotImplementedError("This eventloop doesn't implement synchronous 'run()'.") def run_as_coroutine(self, stdin, callbacks): """ Similar to `run`, but this is a coroutine. (For asyncio integration.) """ raise NotImplementedError("This eventloop doesn't implement 'run_as_coroutine()'.") @abstractmethod def stop(self): """ Stop the `run` call. (Normally called by :class:`~prompt_toolkit.interface.CommandLineInterface`, when a result is available, or Abort/Quit has been called.) """ @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. """ @abstractmethod def run_in_executor(self, callback): """ 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 `datetime` instance. For interal 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.) """ prompt_toolkit-0.57/prompt_toolkit/renderer.py0000644000175000017500000004403612642300054023432 0ustar jonathanjonathan00000000000000""" Renders the command line on the console. (Redraws parts of the input line that were changed.) """ from __future__ import unicode_literals from pygments.token import Token from prompt_toolkit.layout.screen import Point, Screen, WritePosition from prompt_toolkit.layout.mouse_handlers import MouseHandlers from prompt_toolkit.output import Output from prompt_toolkit.utils import is_windows from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.styles import Style __all__ = ( 'Renderer', 'print_tokens', ) def output_screen_diff(output, screen, current_pos, previous_screen=None, last_char=None, is_done=False, attrs_for_token=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_char: `Char` instance that represents the output attributes of the last drawn character. (Color/attributes.) :param attrs_for_token: :class:`._TokenToAttrsCache` instance. :param width: The width of the terminal. :param prevous_width: The width of the terminal during the last rendering. """ width, height = size.columns, size.rows #: Remember the last printed character. last_char = [last_char] # nonlocal background_turned_on = [False] # Nonlocal #: Variable for capturing the output. write = output.write # 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_char[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 meight 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 token, it also has the # same style, so we don't output it. if last_char[0] and last_char[0].token == char.token: write(char.char) else: attrs = attrs_for_token[char.token] _output_set_attributes(attrs) # If we print something with a background color, remember that. background_turned_on[0] = bool(attrs.bgcolor) write(char.char) last_char[0] = char # Disable autowrap if not previous_screen: output.disable_autowrap() reset_attributes() # When the previous screen has a different size, redraw everything anyway. # Also when we are done. (We meight 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(0, 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, r in enumerate(range(0, row_count)): new_row = screen.data_buffer[r] previous_row = previous_screen.data_buffer[r] 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.token != old_char.token: current_pos = move_cursor(Point(y=y, x=c)) output_char(new_char) current_pos = current_pos._replace(x=current_pos.x + char_width) 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(y=y, x=new_max_line_len+1)) 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(y=current_height - 1, x=0)) # Move cursor: if is_done: current_pos = move_cursor(Point(y=current_height, x=0)) output.erase_down() else: current_pos = move_cursor(screen.cursor_position) if is_done: reset_attributes() output.enable_autowrap() # If the last printed character has a background color, always reset. # (Many terminals give weird artifacs on resize events when there is an # active background color.) if background_turned_on[0]: reset_attributes() if screen.show_cursor or is_done: output.show_cursor() return current_pos, last_char[0] class HeightIsUnknownError(Exception): " Information unavailable. Did not yet receive the CPR response. " class _TokenToAttrsCache(dict): """ A cache structure that maps Pygments Tokens to :class:`.Attr`. (This is an important speed up.) """ def __init__(self, get_style_for_token): self.get_style_for_token = get_style_for_token def __missing__(self, token): try: result = self.get_style_for_token(token) except KeyError: result = None self[token] = result return result class Renderer(object): """ Typical usage: :: output = Vt100_Output.from_pty(sys.stdout) r = Renderer(style, output) r.render(cli, layout=...) """ def __init__(self, style, output, use_alternate_screen=False, mouse_support=False): assert isinstance(style, Style) assert isinstance(output, Output) self.style = style self.output = output self.use_alternate_screen = use_alternate_screen self.mouse_support = to_cli_filter(mouse_support) self._in_alternate_screen = False self._mouse_support_enabled = False self._bracketed_paste_enabled = False self.reset(_scroll=True) def reset(self, _scroll=False): # 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_char = None # When the style hash changes, we have to do a full redraw as well as # clear the `_attrs_for_token` dictionary. self._last_style_hash = None self._attrs_for_token = None # Default MouseHandlers. (Just empty.) self.mouse_handlers = MouseHandlers() # Remember the last title. Only set the title when it changes. self._last_title = None #: 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 Windown, 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: 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 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.use_alternate_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. 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 # For Win32, we have an API call to get the number of rows below the # cursor. if is_windows(): self._min_available_height = self.output.get_rows_below_cursor_position() else: if self.use_alternate_screen: self._min_available_height = self.output.get_size().rows else: # Asks for a cursor position report (CPR). self.output.ask_for_cpr() 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.) """ # 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 self._min_available_height = rows_below_cursor def render(self, cli, 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.use_alternate_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(cli) 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() if is_done: height = 0 # When we are done, we don't necessary want to fill up until the bottom. else: height = self._last_screen.height if self._last_screen else 0 height = max(self._min_available_height, height) # 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, 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: self._last_screen = None self._attrs_for_token = None if self._attrs_for_token is None: self._attrs_for_token = _TokenToAttrsCache(self.style.get_attrs_for_token) self._last_style_hash = self.style.invalidation_hash() layout.write_to_screen(cli, screen, mouse_handlers, WritePosition( xpos=0, ypos=0, width=size.columns, height=(size.rows if self.use_alternate_screen else height), extended_height=size.rows, )) # When grayed. Replace all tokens in the new screen. if cli.is_aborting or cli.is_exiting: screen.replace_all_tokens(Token.Aborted) # Process diff and write to output. self._cursor_pos, self._last_char = output_screen_diff( output, screen, self._cursor_pos, self._last_screen, self._last_char, is_done, attrs_for_token=self._attrs_for_token, 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 # Write title if it changed. new_title = cli.terminal_title if new_title != self._last_title: if new_title is None: self.output.clear_title() else: self.output.set_title(new_title) self._last_title = new_title output.flush() def erase(self): """ 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.) """ output = self.output output.cursor_backward(self._cursor_pos.x) output.cursor_up(self._cursor_pos.y) output.erase_down() output.reset_attributes() output.flush() # Erase title. if self._last_title: output.clear_title() self.reset() 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_tokens(output, tokens, style): """ Print a list of (Token, text) tuples in the given style to the output. """ assert isinstance(output, Output) assert isinstance(style, Style) # Reset first. output.reset_attributes() output.enable_autowrap() # Print all (token, text) tuples. attrs_for_token = _TokenToAttrsCache(style.get_attrs_for_token) for token, text in tokens: attrs = attrs_for_token[token] if attrs: output.set_attributes(attrs) else: output.reset_attributes() output.write(text) # Reset again. output.reset_attributes() output.flush() prompt_toolkit-0.57/prompt_toolkit/application.py0000644000175000017500000001442712642107716024142 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from .buffer import Buffer, AcceptAction from .buffer_mapping import BufferMapping from .clipboard import Clipboard, InMemoryClipboard from .enums import DEFAULT_BUFFER from .filters import CLIFilter, to_cli_filter from .key_binding.bindings.basic import load_basic_bindings from .key_binding.bindings.emacs import load_emacs_bindings from .key_binding.registry import Registry from .layout import Window from .layout.containers import Container from .layout.controls import BufferControl from .styles import DEFAULT_STYLE, Style from .utils import Callback __all__ = ( 'AbortAction', 'Application', ) class AbortAction(object): """ Actions to take on an Exit or Abort exception. """ RETRY = 'retry' RAISE_EXCEPTION = 'raise-exception' RETURN_NONE = 'return-none' _all = (RETRY, RAISE_EXCEPTION, RETURN_NONE) class Application(object): """ Application class to be passed to a :class:`~prompt_toolkit.interface.CommandLineInterface`. This contains all customizable logic that is not I/O dependent. (So, what is independent of event loops, input and output.) This way, such an :class:`.Application` can run easily on several :class:`~prompt_toolkit.interface.CommandLineInterface` instances, each with a different I/O backends. that runs for instance over telnet, SSH or any other I/O backend. :param layout: A :class:`~prompt_toolkit.layout.containers.Container` instance. :param buffer: A :class:`~prompt_toolkit.buffer.Buffer` instance for the default buffer. :param initial_focussed_buffer: Name of the buffer that is focussed during start-up. :param key_bindings_registry: :class:`~prompt_toolkit.key_binding.registry.Registry` instance for the key bindings. :param clipboard: :class:`~prompt_toolkit.clipboard.base.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 use_alternate_screen: When True, run the application on the alternate screen buffer. :param get_title: Callable that returns the current title to be displayed in the terminal. Filters: :param mouse_support: (:class:`~prompt_toolkit.filters.CLIFilter` or boolean). When True, enable mouse support. :param paste_mode: :class:`~prompt_toolkit.filters.CLIFilter` or boolean. :param ignore_case: :class:`~prompt_toolkit.filters.CLIFilter` or boolean. Callbacks: :param on_input_timeout: Called when there is no input for x seconds. (Fired when any eventloop.onInputTimeout is fired.) :param on_start: Called when reading input starts. :param on_stop: Called when reading input ends. :param on_reset: Called during reset. :param on_buffer_changed: Called when the content of a buffer has been changed. :param on_initialize: Called after the :class:`~prompt_toolkit.interface.CommandLineInterface` initializes. """ def __init__(self, layout=None, buffer=None, buffers=None, initial_focussed_buffer=DEFAULT_BUFFER, style=None, key_bindings_registry=None, clipboard=None, on_abort=AbortAction.RAISE_EXCEPTION, on_exit=AbortAction.RAISE_EXCEPTION, use_alternate_screen=False, mouse_support=False, get_title=None, paste_mode=False, ignore_case=False, on_input_timeout=None, on_start=None, on_stop=None, on_reset=None, on_initialize=None, on_buffer_changed=None): paste_mode = to_cli_filter(paste_mode) ignore_case = to_cli_filter(ignore_case) mouse_support = to_cli_filter(mouse_support) assert layout is None or isinstance(layout, Container) assert buffer is None or isinstance(buffer, Buffer) assert buffers is None or isinstance(buffers, (dict, BufferMapping)) assert key_bindings_registry is None or isinstance(key_bindings_registry, Registry) assert clipboard is None or isinstance(clipboard, Clipboard) assert on_abort in AbortAction._all assert on_exit in AbortAction._all assert isinstance(use_alternate_screen, bool) assert get_title is None or callable(get_title) assert isinstance(paste_mode, CLIFilter) assert isinstance(ignore_case, CLIFilter) assert on_start is None or isinstance(on_start, Callback) assert on_stop is None or isinstance(on_stop, Callback) assert on_reset is None or isinstance(on_reset, Callback) assert on_buffer_changed is None or isinstance(on_buffer_changed, Callback) assert on_initialize is None or isinstance(on_initialize, Callback) assert style is None or isinstance(style, Style) self.layout = layout or Window(BufferControl()) # Make sure that the 'buffers' dictionary is a BufferMapping. self.buffer = buffer or Buffer(accept_action=AcceptAction.RETURN_DOCUMENT) if not buffers or not isinstance(buffers, BufferMapping): self.buffers = BufferMapping(buffers, initial=initial_focussed_buffer) else: self.buffers = buffers if buffer: self.buffers[DEFAULT_BUFFER] = buffer self.initial_focussed_buffer = initial_focussed_buffer self.style = style or DEFAULT_STYLE if key_bindings_registry is None: key_bindings_registry = Registry() load_basic_bindings(key_bindings_registry) load_emacs_bindings(key_bindings_registry) if get_title is None: get_title = lambda: None self.key_bindings_registry = key_bindings_registry self.clipboard = clipboard or InMemoryClipboard() self.on_abort = on_abort self.on_exit = on_exit self.use_alternate_screen = use_alternate_screen self.mouse_support = mouse_support self.get_title = get_title self.paste_mode = paste_mode self.ignore_case = ignore_case self.on_input_timeout = on_input_timeout or Callback() self.on_start = on_start or Callback() self.on_stop = on_stop or Callback() self.on_reset = on_reset or Callback() self.on_initialize = on_initialize or Callback() self.on_buffer_changed = on_buffer_changed or Callback() prompt_toolkit-0.57/prompt_toolkit/interface.py0000644000175000017500000010061112642645762023576 0ustar jonathanjonathan00000000000000""" The main `CommandLineInterface` class and logic. """ from __future__ import unicode_literals import functools import os import signal import six import sys import textwrap import threading import weakref import datetime from .application import Application, AbortAction from .buffer import Buffer from .buffer_mapping import BufferMapping from .completion import CompleteEvent from .completion import get_common_complete_suffix from .enums import SEARCH_BUFFER from .eventloop.base import EventLoop from .eventloop.callbacks import EventLoopCallbacks from .filters import Condition from .input import StdinInput, Input from .key_binding.input_processor import InputProcessor from .output import Output from .renderer import Renderer, print_tokens from .search_state import SearchState from .utils import is_windows, Callback # Following import is required for backwards compatibility. from .buffer import AcceptAction __all__ = ( 'AbortAction', 'CommandLineInterface', ) class CommandLineInterface(object): """ Wrapper around all the other classes, tying everything together. Typical usage:: application = Application(...) cli = CommandLineInterface(application, eventloop) result = cli.run() print(result) :param application: :class:`~prompt_toolkit.application.Application` instance. :param eventloop: The :class:`~prompt_toolkit.eventloop.base.EventLoop` to be used when `run` is called. :param input: :class:`~prompt_toolkit.input.Input` instance. :param output: :class:`~prompt_toolkit.output.Output` instance. (Probably Vt100_Output or Win32Output.) """ def __init__(self, application, eventloop=None, input=None, output=None): assert isinstance(application, Application) assert eventloop is None or isinstance(eventloop, EventLoop) assert output is None or isinstance(output, Output) assert input is None or isinstance(input, Input) from .shortcuts import create_output, create_eventloop self.application = application self.eventloop = eventloop or create_eventloop() self._is_running = False # Inputs and outputs. self.output = output or create_output() self.input = input or StdinInput(sys.stdin) #: The input buffers. assert isinstance(application.buffers, BufferMapping) self.buffers = application.buffers #: The `Renderer` instance. # Make sure that the same stdout is used, when a custom renderer has been passed. self.renderer = Renderer( self.application.style, self.output, use_alternate_screen=application.use_alternate_screen, mouse_support=application.mouse_support) #: 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 #: When there is high CPU, postpone the renderering max x seconds. #: '0' means: don't postpone. '.5' means: try to draw at least twice a second. self.max_render_postpone_time = 0 # E.g. .5 # Invalidate flag. When 'True', a repaint has been scheduled. self._invalidated = False self.on_invalidate = Callback() # Invalidate event. #: The `InputProcessor` instance. self.input_processor = InputProcessor(application.key_bindings_registry, weakref.ref(self)) self._async_completers = {} # Map buffer name to completer function. # Pointer to sub CLI. (In chain of CLI instances.) self._sub_cli = None # None or other CommandLineInterface instance. # Call `add_buffer` for each buffer. for name, b in self.buffers.items(): self.add_buffer(name, b) self.reset() # Trigger initialize callback. self.application.on_initialize.fire(self) @property def layout(self): return self.application.layout @property def clipboard(self): return self.application.clipboard def add_buffer(self, name, buffer, focus=False): """ Insert a new buffer. """ assert isinstance(buffer, Buffer) self.buffers[name] = buffer if focus: self.buffers.focus(name) # Create asynchronous completer / auto suggestion. auto_suggest_function = self._create_auto_suggest_function(buffer) completer_function = self._create_async_completer(buffer) self._async_completers[name] = completer_function # Complete/suggest on text insert. def create_on_insert_handler(): """ Wrapper around the asynchronous completer and auto suggestion, that ensures that it's only called while typing if the `complete_while_typing` filter is enabled. """ def on_text_insert(): # Only complete when "complete_while_typing" is enabled. if buffer.completer and buffer.complete_while_typing(): completer_function() # Call auto_suggest. if buffer.auto_suggest: auto_suggest_function() # Trigger on_buffer_changed when text in this buffer changes. self.application.on_buffer_changed.fire(self) return on_text_insert buffer.on_text_insert += create_on_insert_handler() def start_completion(self, buffer_name=None, 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.) """ buffer_name = buffer_name or self.current_buffer_name completer = self._async_completers.get(buffer_name) if completer: completer(select_first=select_first, select_last=select_last, insert_common_part=insert_common_part, complete_event=CompleteEvent(completion_requested=True)) @property def current_buffer_name(self): """ The name of the current :class:`.Buffer`. (Or `None`.) """ return self.buffers.current_name(self) @property def current_buffer(self): """ The currently focussed :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.buffers.current(self) def focus(self, buffer_name): """ Focus the buffer with the given name on the focus stack. """ self.buffers.focus(self, buffer_name) def push_focus(self, buffer_name): """ Push to the focus stack. """ self.buffers.push_focus(self, buffer_name) def pop_focus(self): """ Pop from the focus stack. """ self.buffers.pop_focus(self) @property def terminal_title(self): """ Return the current title to be displayed in the terminal. When this in `None`, the terminal title remains the original. """ result = self.application.get_title() # Make sure that this function returns a unicode object, # and not a byte string. assert result is None or isinstance(result, six.text_type) return result @property def is_searching(self): """ True when we are searching. """ return self.current_buffer_name == SEARCH_BUFFER def reset(self, reset_current_buffer=False): """ Reset everything, for reading the next input. :param reset_current_buffer: If True, also reset the focussed buffer. """ # 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_flag = False self._abort_flag = False self._return_value = None self.renderer.reset() self.input_processor.reset() self.layout.reset() if reset_current_buffer: self.current_buffer.reset() # Search new search state. (Does also remember what has to be # highlighted.) self.search_state = SearchState(ignore_case=Condition(lambda: self.is_ignoring_case)) # Trigger reset event. self.application.on_reset.fire(self) @property def in_paste_mode(self): """ True when we are in paste mode. """ return self.application.paste_mode(self) @property def is_ignoring_case(self): """ True when we currently ignore casing. """ return self.application.ignore_case(self) 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() if self.eventloop is not None: def redraw(): self._invalidated = False self._redraw() # Call redraw in the eventloop (thread safe). # Give it low priority. If there is other I/O or CPU intensive # stuff to handle, give that priority, but max postpone x seconds. _max_postpone_until = datetime.datetime.now() + datetime.timedelta( seconds=self.max_render_postpone_time) self.eventloop.call_from_executor(redraw, _max_postpone_until=_max_postpone_until) # Depracated alias for 'invalidate'. request_redraw = invalidate def _redraw(self): """ Render the command line again. (Not thread safe!) (From other threads, or if unsure, use :meth:`.CommandLineInterface.invalidate`.) """ # Only draw when no sub application was started. if self._is_running and self._sub_cli is None: self.render_counter += 1 self.renderer.render(self, self.layout, is_done=self.is_done) 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() self.renderer.request_absolute_cursor_position() self._redraw() def run(self, reset_current_buffer=True, pre_run=None): """ Read input from the command line. This runs the eventloop until a return value has been set. :param reset_current_buffer: Reset content of current buffer. :param pre_run: Callable that is called right after the reset has taken place. This allows custom initialisation. """ assert pre_run is None or callable(pre_run) try: self._is_running = True self.application.on_start.fire(self) self.reset(reset_current_buffer=reset_current_buffer) # Call pre_run. if pre_run: pre_run() # Run eventloop in raw mode. with self.input.raw_mode(): self.renderer.request_absolute_cursor_position() self._redraw() self.eventloop.run(self.input, self.create_eventloop_callbacks()) finally: # Clean up renderer. (This will leave the alternate screen, if we use # that.) self.renderer.reset() self.application.on_stop.fire(self) self._is_running = False # Return result. return self.return_value() try: # The following `run_async` function is compiled at runtime # because it contains syntax which is not supported on older Python # versions. (A 'return' inside a generator.) six.exec_(textwrap.dedent(''' def run_async(self, reset_current_buffer=True, pre_run=None): """ Same as `run`, but this returns a coroutine. This is only available on Python >3.3, with asyncio. """ assert pre_run is None or callable(pre_run) try: self._is_running = True self.application.on_start.fire(self) self.reset(reset_current_buffer=reset_current_buffer) # Call pre_run. if pre_run: pre_run() with self.input.raw_mode(): self.renderer.request_absolute_cursor_position() self._redraw() yield from self.eventloop.run_as_coroutine( self.input, self.create_eventloop_callbacks()) return self.return_value() finally: self.renderer.reset() self.application.on_stop.fire(self) self._is_running = False try: import asyncio except ImportError: pass else: run_async = asyncio.coroutine(run_async) ''')) except SyntaxError: # Python2, or early versions of Python 3. def run_async(self, reset_current_buffer=True, pre_run=None): """ Same as `run`, but this returns a coroutine. This is only available on Python >3.3, with asyncio. """ raise NotImplementedError def run_sub_application(self, application, done_callback=None): """ Run a sub :class:`~prompt_toolkit.application.Application`. This will suspend the main application and display the sub application until that one returns a value. The value is returned by calling `done_callback` with the result. The sub application will share the same I/O of the main application. That means, it uses the same input and output channels and it shares the same event loop. .. note:: Technically, it gets another Eventloop instance, but that is only a proxy to our main event loop. The reason is that calling 'stop' --which returns the result of an application when it's done-- is handled differently. """ assert isinstance(application, Application) assert done_callback is None or callable(done_callback) if self._sub_cli is not None: raise RuntimeError('Another sub application started already.') # Erase current application. self.renderer.erase() # Callback when the sub app is done. def done(): # Redraw sub app in done state. # and reset the renderer. (This reset will also quit the alternate # screen, if the sub application used that.) sub_cli._redraw() sub_cli.renderer.reset() sub_cli._is_running = False # Don't render anymore. self._sub_cli = None # Restore main application. self.renderer.request_absolute_cursor_position() self._redraw() # Deliver result. if done_callback: done_callback(sub_cli.return_value()) # Create sub CommandLineInterface. sub_cli = CommandLineInterface( application=application, eventloop=_SubApplicationEventLoop(self, done), input=self.input, output=self.output) sub_cli._is_running = True # Allow rendering of sub app. sub_cli._redraw() self._sub_cli = sub_cli def exit(self): """ Set exit. When Control-D has been pressed. """ on_exit = self.application.on_exit self._exit_flag = True self._redraw() if on_exit == AbortAction.RAISE_EXCEPTION: def eof_error(): raise EOFError() self._set_return_callable(eof_error) elif on_exit == AbortAction.RETRY: self.reset() self.renderer.request_absolute_cursor_position() self.current_buffer.reset() elif on_exit == AbortAction.RETURN_NONE: self.set_return_value(None) def abort(self): """ Set abort. When Control-C has been pressed. """ on_abort = self.application.on_abort self._abort_flag = True self._redraw() if on_abort == AbortAction.RAISE_EXCEPTION: def keyboard_interrupt(): raise KeyboardInterrupt() self._set_return_callable(keyboard_interrupt) elif on_abort == AbortAction.RETRY: self.reset() self.renderer.request_absolute_cursor_position() self.current_buffer.reset() elif on_abort == AbortAction.RETURN_NONE: self.set_return_value(None) # Deprecated aliase for exit/abort. set_exit = exit set_abort = abort def set_return_value(self, document): """ Set a return value. The eventloop can retrieve the result it by calling `return_value`. """ self._set_return_callable(lambda: document) self._redraw() # Redraw in "done" state, after the return value has been set. def _set_return_callable(self, value): assert callable(value) self._return_value = value if self.eventloop: self.eventloop.stop() def run_in_terminal(self, func, render_cli_done=False): """ Run function on the terminal above the 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. :returns: the result of `func`. """ # Draw interface in 'done' state, or erase. if render_cli_done: self._return_value = True self._redraw() self.renderer.reset() # Make sure to disable mouse mode, etc... else: self.renderer.erase() # Run system command. with self.input.cooked_mode(): result = func() self._return_value = None # Redraw interface again. self.renderer.reset() self.renderer.request_absolute_cursor_position() self._redraw() return result def run_system_command(self, command): """ Run system command (While hiding the prompt. When finished, all the output will scroll above the prompt.) :param command: Shell command to be executed. """ def run(): if is_windows(): os.system(command) # Needs to be unicode for win32 else: os.system(command.encode('utf-8')) try: six.moves.input('\nPress ENTER to continue...') except EOFError: pass self.run_in_terminal(run) def suspend_to_background(self): """ (Not thread safe -- to be called from inside the key bindings.) Suspend process. """ # Only suspend when the opperating system supports it. # (Not on Windows.) if hasattr(signal, 'SIGTSTP'): def run(): # Send `SIGSTP` to own process. # This will cause it to suspend. os.kill(os.getpid(), signal.SIGTSTP) self.run_in_terminal(run) def print_tokens(self, tokens, style=None): """ Print a list of (Token, 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 style: Style class to use. Defaults to the active style in the CLI. """ print_tokens(self.output, tokens, style or self.application.style) @property def is_exiting(self): """ ``True`` when the exit flag as been set. """ return self._exit_flag @property def is_aborting(self): """ ``True`` when the abort flag as been set. """ return self._abort_flag @property def is_returning(self): """ ``True`` when a return value has been set. """ return self._return_value is not None def return_value(self): """ Get the return value. Not that this method can throw an exception. """ # Note that it's a method, not a property, because it can throw # exceptions. if self._return_value: return self._return_value() @property def is_done(self): return self.is_exiting or self.is_aborting or self.is_returning def _create_async_completer(self, buffer): """ Create function for asynchronous autocompletion. (Autocomplete in other thread.) """ complete_thread_running = [False] # By ref. def async_completer(select_first=False, select_last=False, insert_common_part=False, complete_event=None): document = buffer.document complete_event = complete_event or CompleteEvent(text_inserted=True) # Don't start two threads at the same time. if complete_thread_running[0]: return # Don't complete when we already have completions. if buffer.complete_state or not buffer.completer: return # Otherwise, get completions in other thread. complete_thread_running[0] = True def run(): completions = list(buffer.completer.get_completions(document, complete_event)) complete_thread_running[0] = False def callback(): """ Set the new complete_state in a safe way. Don't replace an existing complete_state if we had one. (The user could have pressed 'Tab' in the meantime. Also don't set it if the text was changed in the meantime. """ # Set completions if the text was not yet changed. if buffer.text == document.text and \ buffer.cursor_position == document.cursor_position and \ not buffer.complete_state: set_completions = True select_first_anyway = False # When the commond part has to be inserted, and there # is a common part. if insert_common_part: common_part = get_common_complete_suffix(document, completions) if common_part: # Insert + run completer again. buffer.insert_text(common_part) async_completer() set_completions = False 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 * expension, like '*.py', with # exactly one match.) if len(completions) == 1: select_first_anyway = True if set_completions: buffer.set_completions( completions=completions, go_to_first=select_first or select_first_anyway, go_to_last=select_last) self.invalidate() else: # Otherwise, restart thread. async_completer() if self.eventloop: self.eventloop.call_from_executor(callback) self.eventloop.run_in_executor(run) return async_completer def _create_auto_suggest_function(self, buffer): """ Create function for asynchronous auto suggestion. (AutoSuggest in other thread.) """ suggest_thread_running = [False] # By ref. def async_suggestor(): document = buffer.document # Don't start two threads at the same time. if suggest_thread_running[0]: return # Don't suggest when we already have a suggestion. if buffer.suggestion or not buffer.auto_suggest: return # Otherwise, get completions in other thread. suggest_thread_running[0] = True def run(): suggestion = buffer.auto_suggest.get_suggestion(self, buffer, document) suggest_thread_running[0] = False def callback(): # Set suggestion only if the text was not yet changed. if buffer.text == document.text and \ buffer.cursor_position == document.cursor_position: # Set suggestion and redraw interface. buffer.suggestion = suggestion self.invalidate() else: # Otherwise, restart thread. async_suggestor() if self.eventloop: self.eventloop.call_from_executor(callback) self.eventloop.run_in_executor(run) return async_suggestor def stdout_proxy(self, raw=False): """ Create an :class:`_StdoutProxy` class which can be used as a patch for `sys.stdout`. 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. :param raw: (`bool`) When True, vt100 terminal escape sequences are not removed/escaped. """ return _StdoutProxy(self, raw=raw) def patch_stdout_context(self, raw=False): """ Return a context manager that will replace ``sys.stdout`` with a proxy that makes sure that all printed text will appear above the prompt, and that it doesn't destroy the output from the renderer. """ return _PatchStdoutContext(self.stdout_proxy(raw=raw)) def create_eventloop_callbacks(self): return _InterfaceEventLoopCallbacks(self) class _InterfaceEventLoopCallbacks(EventLoopCallbacks): """ Callbacks on the :class:`.CommandLineInterface` object, to which an eventloop can talk. """ def __init__(self, cli): assert isinstance(cli, CommandLineInterface) self.cli = cli @property def _active_cli(self): """ Return the active `CommandLineInterface`. """ cli = self.cli # If there is a sub CLI. That one is always active. while cli._sub_cli: cli = cli._sub_cli return cli def terminal_size_changed(self): """ Report terminal size change. This will trigger a redraw. """ self._active_cli._on_resize() def input_timeout(self): cli = self._active_cli cli.application.on_input_timeout.fire(cli) def feed_key(self, key_press): """ Feed a key press to the CommandLineInterface. """ # Feed the key and redraw. self._active_cli.input_processor.feed_key(key_press) class _PatchStdoutContext(object): def __init__(self, new_stdout): self.new_stdout = new_stdout def __enter__(self): self.original_stdout = sys.stdout sys.stdout = self.new_stdout def __exit__(self, *a, **kw): sys.stdout = self.original_stdout class _StdoutProxy(object): """ Proxy for stdout, as returned by :class:`CommandLineInterface.stdout_proxy`. """ def __init__(self, cli, raw=False): assert isinstance(cli, CommandLineInterface) assert isinstance(raw, bool) self._lock = threading.RLock() self._cli = cli self._raw = raw self._buffer = [] self.errors = sys.__stdout__.errors self.encoding = sys.__stdout__.encoding def _do(self, func): if self._cli._is_running: run_in_terminal = functools.partial(self._cli.run_in_terminal, func) self._cli.eventloop.call_from_executor(run_in_terminal) else: func() 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 overwritter 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] def run(): for s in to_write: if self._raw: self._cli.output.write_raw(s) else: self._cli.output.write(s) self._do(run) else: # Otherwise, cache in buffer. self._buffer.append(data) def write(self, data): with self._lock: self._write(data) def _flush(self): def run(): for s in self._buffer: if self._raw: self._cli.output.write_raw(s) else: self._cli.output.write(s) self._buffer = [] self._cli.output.flush() self._do(run) def flush(self): """ Flush buffered output. """ with self._lock: self._flush() class _SubApplicationEventLoop(EventLoop): """ Eventloop used by sub applications. A sub application is an `Application` that is "spawned" by a parent application. The parent application is suspended temporarily and the sub application is displayed instead. It doesn't need it's own event loop. The `EventLoopCallbacks` from the parent application are redirected to the sub application. So if the event loop that is run by the parent application detects input, the callbacks will make sure that it's forwarded to the sub application. When the sub application has a return value set, it will terminate by calling the `stop` method of this event loop. This is used to transfer control back to the parent application. """ def __init__(self, cli, stop_callback): assert isinstance(cli, CommandLineInterface) assert callable(stop_callback) self.cli = cli self.stop_callback = stop_callback def stop(self): self.stop_callback() def close(self): pass def run_in_executor(self, callback): self.cli.eventloop.run_in_executor(callback) def call_from_executor(self, callback, _max_postpone_until=None): self.cli.eventloop.call_from_executor( callback, _max_postpone_until=_max_postpone_until) def add_reader(self, fd, callback): self.cli.eventloop.add_reader(fd, callback) def remove_reader(self, fd): self.cli.eventloop.remove_reader(fd) prompt_toolkit-0.57/prompt_toolkit/input.py0000644000175000017500000000441612623240275022770 0ustar jonathanjonathan00000000000000""" Abstraction of CLI Input. """ from __future__ import unicode_literals from .utils import DummyContext, is_windows from abc import ABCMeta, abstractmethod from six import with_metaclass import os import sys if is_windows(): from .terminal.win32_input import raw_mode, cooked_mode else: from .terminal.vt100_input import raw_mode, cooked_mode __all__ = ( 'Input', 'StdinInput', 'PipeInput', ) 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.interface.CommandLineInterface` 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 read(self): """ Return text from the input. """ @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. """ class StdinInput(Input): """ Simple wrapper around stdin. """ def __init__(self, stdin=None): self.stdin = stdin or sys.stdin def __repr__(self): return 'StdinInput(stdin=%r)' % (self.stdin,) 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 read(self): return self.stdin.read() class PipeInput(Input): """ Input that is send through a pipe. This is useful if we want to send the input programatically into the interface, but still use the eventloop. Usage:: input = PipeInput() input.send('inputdata') """ def __init__(self): self._r, self._w = os.pipe() def fileno(self): return self._r def read(self): return os.read(self._r) def send(self, data): os.write(self._w, data.encode('utf-8')) def raw_mode(self): return DummyContext() def cooked_mode(self): return DummyContext() prompt_toolkit-0.57/prompt_toolkit/validation.py0000644000175000017500000000333212623240275023757 0ustar jonathanjonathan00000000000000""" Input validation for a `Buffer`. (Validators will be called before accepting input.) """ from __future__ import unicode_literals from .filters import to_simple_filter from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'ConditionalValidator', 'ValidationError', 'Validator', ) class ValidationError(Exception): """ Error raised by :meth:`.Validator.validate`. :param cursor_position: The cursor position where the error occured. :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. """ @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 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_simple_filter(filter) def validate(self, document): # Call the validator only if the filter is active. if self.filter(): self.validator.validate(document) prompt_toolkit-0.57/prompt_toolkit/keys.py0000644000175000017500000000525012623240275022601 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals __all__ = ( 'Key', 'Keys', ) class Key(object): def __init__(self, name): #: Descriptive way of writing keys in configuration files. e.g. #: for ``Control-A``. self.name = name def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.name) class Keys(object): Escape = Key('') ControlA = Key('') ControlB = Key('') ControlC = Key('') ControlD = Key('') ControlE = Key('') ControlF = Key('') ControlG = Key('') ControlH = Key('') ControlI = Key('') # Tab ControlJ = Key('') # Enter ControlK = Key('') ControlL = Key('') ControlM = Key('') # Enter ControlN = Key('') ControlO = Key('') ControlP = Key('') ControlQ = Key('') ControlR = Key('') ControlS = Key('') ControlT = Key('') ControlU = Key('') ControlV = Key('') ControlW = Key('') ControlX = Key('') ControlY = Key('') ControlZ = Key('') ControlSpace = Key('') ControlBackslash = Key('') ControlSquareClose = Key('') ControlCircumflex = Key('') ControlUnderscore = Key('') ControlLeft = Key('') ControlRight = Key('') ControlUp = Key('') ControlDown = Key('') Up = Key('') Down = Key('') Right = Key('') Left = Key('') Home = Key('') End = Key('') Delete = Key('') ShiftDelete = Key('') PageUp = Key('') PageDown = Key('') BackTab = Key('') # shift + tab Insert = Key('') Tab = ControlI Backspace = ControlH F1 = Key('') F2 = Key('') F3 = Key('') F4 = Key('') F5 = Key('') F6 = Key('') F7 = Key('') F8 = Key('') F9 = Key('') F10 = Key('') F11 = Key('') F12 = Key('') F13 = Key('') F14 = Key('') F15 = Key('') F16 = Key('') F17 = Key('') F18 = Key('') F19 = Key('') F20 = Key('') # Matches any key. Any = Key('') # Special CPRResponse = Key('') Vt100MouseEvent = Key('') WindowsMouseEvent = Key('') BracketedPaste = Key('') prompt_toolkit-0.57/prompt_toolkit/mouse_events.py0000644000175000017500000000242012625730370024340 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__ = ( 'MouseEventTypes', 'MouseEvent' ) class MouseEventTypes: 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-0.57/prompt_toolkit/history.py0000644000175000017500000000543112623240275023330 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass import datetime import os __all__ = ( 'FileHistory', 'History', 'InMemoryHistory', ) class History(with_metaclass(ABCMeta, object)): """ Base ``History`` interface. """ @abstractmethod def append(self, string): " Append string to history. " @abstractmethod def __getitem__(self, key): " Return one item of the history. It should be accessible like a `list`. " @abstractmethod def __iter__(self): " Iterate through all the items of the history. Cronologically. " @abstractmethod def __len__(self): " Return the length of the history. " def __bool__(self): """ Never evaluate to False, even when the history is empty. (Python calls __len__ if __bool__ is not implemented.) This is mainly to allow lazy evaluation:: x = history or InMemoryHistory() """ return True __nonzero__ = __bool__ # For Python 2. class InMemoryHistory(History): """ :class:`.History` class that keeps a list of all strings in memory. """ def __init__(self): self.strings = [] def append(self, string): self.strings.append(string) def __getitem__(self, key): return self.strings[key] def __iter__(self): return iter(self.strings) def __len__(self): return len(self.strings) class FileHistory(History): """ :class:`.History` class that stores all strings in a file. """ def __init__(self, filename): self.strings = [] self.filename = filename self._load() def _load(self): lines = [] def add(): if lines: # Join and drop trailing newline. string = ''.join(lines)[:-1] self.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() def append(self, string): self.strings.append(string) # Save to file. with open(self.filename, 'ab') as f: write = lambda t: f.write(t.encode('utf-8')) write('\n# %s\n' % datetime.datetime.now()) for line in string.split('\n'): write('+%s\n' % line) def __getitem__(self, key): return self.strings[key] def __iter__(self): return iter(self.strings) def __len__(self): return len(self.strings) prompt_toolkit-0.57/prompt_toolkit/layout/0000755000175000017500000000000012642647210022570 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/layout/utils.py0000644000175000017500000000654412641067653024321 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.utils import get_cwidth __all__ = ( 'token_list_len', 'token_list_width', 'token_list_to_text', 'explode_tokens', 'find_window_for_buffer_name', ) def token_list_len(tokenlist): """ Return the amount of characters in this token list. :param tokenlist: List of (token, text) or (token, text, mouse_handler) tuples. """ return sum(len(item[1]) for item in tokenlist) def token_list_width(tokenlist): """ Return the character width of this token list. (Take double width characters into account.) :param tokenlist: List of (token, text) or (token, text, mouse_handler) tuples. """ return sum(get_cwidth(c) for item in tokenlist for c in item[1]) def token_list_to_text(tokenlist): """ Concatenate all the text parts again. """ return ''.join(item[1] for item in tokenlist) def iter_token_lines(tokenlist): """ Iterator that yields tokenlists for each line. """ line = [] for token, c in explode_tokens(tokenlist): line.append((token, c)) if c == '\n': yield line line = [] yield line def split_lines(tokenlist): """ Take a single list of (Token, text) tuples and yield one such list for each line. :param tokenlist: List of (token, text) or (token, text, mouse_handler) tuples. """ line = [] for item in tokenlist: # For (token, text) tuples. if len(item) == 2: token, string = item parts = string.split('\n') for part in parts[:-1]: if part: line.append((token, part)) yield line line = [] line.append((token, parts[-1])) # For (token, text, mouse_handler) tuples. # I know, partly copy/paste, but understandable and more efficient # than many tests. else: token, string, mouse_handler = item parts = string.split('\n') for part in parts[:-1]: if part: line.append((token, part, mouse_handler)) yield line line = [] line.append((token, parts[-1], mouse_handler)) if line: yield line def explode_tokens(tokenlist): """ Turn a list of (token, text) tuples into another list where each string is exactly one character. :param tokenlist: List of (token, text) tuples. """ result = [] for token, string in tokenlist: for c in string: result.append((token, c)) return result def find_window_for_buffer_name(cli, buffer_name): """ Look for a :class:`~prompt_toolkit.layout.containers.Window` in the Layout that contains the :class:`~prompt_toolkit.layout.controls.BufferControl` for the given buffer and return it. If no such Window is found, return None. """ from prompt_toolkit.interface import CommandLineInterface assert isinstance(cli, CommandLineInterface) from .containers import Window from .controls import BufferControl for l in cli.layout.walk(cli): if isinstance(l, Window) and isinstance(l.content, BufferControl): if l.content.buffer_name == buffer_name: return l prompt_toolkit-0.57/prompt_toolkit/layout/processors.py0000644000175000017500000003766712636147775025405 0ustar jonathanjonathan00000000000000""" Processors are little transformation blocks that transform the token list from a buffer before the BufferControl will render it to the screen. They can insert tokens before or after, or highlight fragments by replacing the token types. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from pygments.token import Token from prompt_toolkit.document import Document from prompt_toolkit.enums import SEARCH_BUFFER from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.layout.utils import token_list_to_text from .utils import token_list_len __all__ = ( 'Processor', 'Transformation', 'HighlightSearchProcessor', 'HighlightSelectionProcessor', 'PasswordProcessor', 'BracketsMismatchProcessor', 'BeforeInput', 'AfterInput', 'AppendAutoSuggestion', 'ConditionalProcessor', 'ShowLeadingWhiteSpaceProcessor', 'ShowTrailingWhiteSpaceProcessor', ) class Processor(with_metaclass(ABCMeta, object)): """ Manipulate the tokenstream for a :class:`~prompt_toolkit.layout.controls.BufferControl`. """ @abstractmethod def apply_transformation(self, cli, document, tokens): """ Apply transformation. Returns a :class:`.Transformation` instance. """ return Transformation(document, tokens) def has_focus(self, cli): """ Processors can override the focus. (Used for the reverse-i-search prefix in DefaultPrompt.) """ return False def invalidation_hash(self, cli, document): """ Returns a hashable for invalidation. When this changes, the processor has to be applied again or the original input. """ return None 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 `tokens`! :param document: The transformed :class:`~prompt_toolkit.document.Document` instance, to be passed to the next processor. Most of the time, this can be the same as the received document, unless some text has been changed/inserted somewhere. :param tokens: The transformed tokens. 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, document, tokens, source_to_display=None, display_to_source=None): self.document = document self.tokens = tokens self.source_to_display = source_to_display or (lambda i: i) self.display_to_source = display_to_source or (lambda i: i) class HighlightSearchProcessor(Processor): # XXX: Deprecated! """ Processor that highlights search matches in the document. :param preview_search: A Filter; when active it indicates that we take the search text in real time while the user is typing, instead of the last active search state. """ def __init__(self, preview_search=False, search_buffer_name=SEARCH_BUFFER): self.preview_search = to_cli_filter(preview_search) self.search_buffer_name = search_buffer_name def _get_search_text(self, cli): """ The text we are searching for. """ # When the search buffer has focus, take that text. if self.preview_search(cli) and cli.buffers[self.search_buffer_name].text: return cli.buffers[self.search_buffer_name].text # Otherwise, take the text of the last active search. else: return cli.search_state.text def apply_transformation(self, cli, document, tokens): search_text = self._get_search_text(cli) ignore_case = cli.is_ignoring_case if search_text and not cli.is_returning: # For each search match, replace the Token. for index in document.find_all(search_text, ignore_case=ignore_case): if index == document.cursor_position: token = Token.SearchMatch.Current else: token = Token.SearchMatch for x in range(index, index + len(search_text)): if x < len(tokens): tokens[x] = (token, tokens[x][1]) return Transformation(document, tokens) def invalidation_hash(self, cli, document): search_text = self._get_search_text(cli) # When the search state changes, highlighting will be different. return ( search_text, cli.is_returning, # When we search for text, and the cursor position changes. The # processor has to be applied every time again, because the current # match is highlighted in another color. (search_text and document.cursor_position), ) class HighlightSelectionProcessor(Processor): # XXX: Deprecated! """ Processor that highlights the selection in the document. """ def apply_transformation(self, cli, document, tokens): # In case of selection, highlight all matches. selection_range = document.selection_range() if selection_range: from_, to = selection_range for i in range(from_, to): if i < len(tokens): tokens[i] = (Token.SelectedText, tokens[i][1]) return Transformation(document, tokens) def invalidation_hash(self, cli, document): # When the search state changes, highlighting will be different. return ( document.selection_range(), ) 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, cli, document, tokens): # Returns (new_token_list, cursor_index_to_token_index_f) return Transformation( document, [(token, self.char * len(text)) for token, text in tokens]) class HighlightMatchingBracketProcessor(Processor): # XXX: Deprecated! """ When the cursor is on or right after a bracket, it highlights the matching bracket. """ _closing_braces = '])}>' def __init__(self, chars='[](){}<>'): self.chars = chars def apply_transformation(self, cli, document, tokens): def replace_token(pos): """ Replace token in list of tokens. """ tokens[pos] = (Token.MatchingBracket, tokens[pos][1]) def apply_for_document(document): """ Find and replace matching tokens. """ if document.current_char in self.chars: pos = document.matching_bracket_position if pos: replace_token(document.cursor_position) replace_token(document.cursor_position + pos) return True # Apply for character below cursor. applied = apply_for_document(document) # Otherwise, apply for character before cursor. if (not applied and document.cursor_position > 0 and document.char_before_cursor in self._closing_braces): apply_for_document(Document(document.text, document.cursor_position - 1)) return Transformation(document, tokens) def invalidation_hash(self, cli, document): on_brace = document.current_char in self.chars after_brace = document.char_before_cursor in self.chars if on_brace: return (True, document.cursor_position) elif after_brace and document.char_before_cursor in self._closing_braces: return (True, document.cursor_position - 1) else: # Don't include the cursor position in the hash if we are not *on* # a brace. We don't have to rerender the output, because it will be # the same anyway. return False class BracketsMismatchProcessor(Processor): """ Processor that replaces the token type of bracket mismatches by an Error. """ error_token = Token.Error def apply_transformation(self, cli, document, tokens): stack = [] # Pointers to the result array for index, (token, text) in enumerate(tokens): top = tokens[stack[-1]][1] if stack else '' if text in '({[]})': if text in '({[': # Put open bracket on the stack stack.append(index) elif (text == ')' and top == '(' or text == '}' and top == '{' or text == ']' and top == '['): # Match found stack.pop() else: # No match for closing bracket. tokens[index] = (self.error_token, text) # Highlight unclosed tags that are still on the stack. for index in stack: tokens[index] = (Token.Error, tokens[index][1]) return Transformation(document, tokens) class BeforeInput(Processor): """ Insert tokens before the input. :param get_tokens: Callable that takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and returns the list of tokens to be inserted. """ def __init__(self, get_tokens): assert callable(get_tokens) self.get_tokens = get_tokens def apply_transformation(self, cli, document, tokens): tokens_before = self.get_tokens(cli) shift_position = token_list_len(tokens_before) return Transformation( document=document.insert_before(token_list_to_text(tokens_before)), tokens=tokens_before + tokens, source_to_display=lambda i: i + shift_position, display_to_source=lambda i: i - shift_position) @classmethod def static(cls, text, token=Token): """ Create a :class:`.BeforeInput` instance that always inserts the same text. """ def get_static_tokens(cli): return [(token, text)] return cls(get_static_tokens) def __repr__(self): return '%s(get_tokens=%r)' % ( self.__class__.__name__, self.get_tokens) def invalidation_hash(self, cli, document): # Redraw when the given tokens change. return tuple(self.get_tokens(cli)) class AfterInput(Processor): """ Insert tokens after the input. :param get_tokens: Callable that takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and returns the list of tokens to be appended. """ def __init__(self, get_tokens): assert callable(get_tokens) self.get_tokens = get_tokens def apply_transformation(self, cli, document, tokens): tokens_after = self.get_tokens(cli) return Transformation( document=document.insert_after(token_list_to_text(tokens_after)), tokens=tokens + self.get_tokens(cli)) @classmethod def static(cls, text, token=Token): """ Create a :class:`.AfterInput` instance that always inserts the same text. """ def get_static_tokens(cli): return [(token, text)] return cls(get_static_tokens) def __repr__(self): return '%s(get_tokens=%r)' % ( self.__class__.__name__, self.get_tokens) def invalidation_hash(self, cli, document): # Redraw when the given tokens change. return tuple(self.get_tokens(cli)) class AppendAutoSuggestion(Processor): """ Append the auto suggestion to the input. (The user can then press the right arrow the insert the suggestion.) :param buffer_name: The name of the buffer from where we should take the auto suggestion. If not given, we take the current buffer. """ def __init__(self, buffer_name=None, token=Token.AutoSuggestion): self.buffer_name = buffer_name self.token = token def _get_buffer(self, cli): if self.buffer_name: return cli.buffers[self.buffer_name] else: return cli.current_buffer def apply_transformation(self, cli, document, tokens): buffer = self._get_buffer(cli) if buffer.suggestion and buffer.document.is_cursor_at_the_end: suggestion = buffer.suggestion.text else: suggestion = '' return Transformation( document=document.insert_after(suggestion), tokens=tokens + [(self.token, suggestion)]) def invalidation_hash(self, cli, document): buffer = self._get_buffer(cli) # Redraw when the suggestion changes. if buffer.suggestion and document.is_cursor_at_the_end: return buffer.suggestion.text class ShowLeadingWhiteSpaceProcessor(Processor): """ Make leading whitespace visible. """ def __init__(self, token=Token.LeadingWhiteSpace, char='\xb7'): self.token = token self.char = char def apply_transformation(self, cli, document, tokens): # Walk through all te tokens. t = (self.token, self.char) is_start_of_line = True for i in range(len(tokens)): char = tokens[i][1] if is_start_of_line and char == ' ': tokens[i] = t elif char == '\n': is_start_of_line = True else: is_start_of_line = False return Transformation(document, tokens) class ShowTrailingWhiteSpaceProcessor(Processor): """ Make trailing whitespace visible. """ def __init__(self, token=Token.TrailingWhiteSpace, char='\xb7'): self.token = token self.char = char def apply_transformation(self, cli, document, tokens): # Walk backwards through all te tokens. t = (self.token, self.char) is_end_of_line = True for i in range(len(tokens) - 1, -1, -1): char = tokens[i][1] if is_end_of_line and char == ' ': tokens[i] = t elif char == '\n': is_end_of_line = True else: is_end_of_line = False return Transformation(document, tokens) 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(cli): return true_or_false # Wrapt 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.CLIFilter` instance. """ def __init__(self, processor, filter): assert isinstance(processor, Processor) self.processor = processor self.filter = to_cli_filter(filter) def apply_transformation(self, cli, document, tokens): # Run processor when enabled. if self.filter(cli): return self.processor.apply_transformation(cli, document, tokens) else: return Transformation(document, tokens) def has_focus(self, cli): if self.filter(cli): return self.processor.has_focus(cli) else: return False def invalidation_hash(self, cli, document): # When enabled, use the hash of the processor. Otherwise, just use # False. if self.filter(cli): return (True, self.processor.invalidation_hash(cli, document)) else: return False def __repr__(self): return '%s(processor=%r, filter=%r)' % ( self.__class__.__name__, self.processor, self.filter) prompt_toolkit-0.57/prompt_toolkit/layout/screen.py0000644000175000017500000001733212636155701024431 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.utils import get_cwidth from collections import defaultdict, namedtuple from pygments.token import Token import six __all__ = ( 'Point', 'Size', 'Screen', 'Char', ) Point = namedtuple('Point', 'y x') Size = namedtuple('Size', 'rows columns') class Char(object): """ Represent a single character in a :class:`.Screen`. This should be considered immutable. """ __slots__ = ('char', 'token', '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': '^?', # Control backspace } def __init__(self, char=' ', token=Token): # If this character has to be displayed otherwise, take that one. char = self.display_mappings.get(char, char) self.char = char self.token = token # 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.token == other.token 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.token != other.token def __repr__(self): return '%s(%r, %r)' % (self.__class__.__name__, self.char, self.token) class CharCache(dict): """ Cache of :class:`.Char` instances. Mapping of (character, Token) tuples to Char instances. (Char instances should be considered immutable.) """ def __missing__(self, key): c = Char(*key) self[key] = c return c _CHAR_CACHE = CharCache() Transparent = Token.Transparent WriteDataResult = namedtuple('WriteDataResult', 'indexes_to_pos line_lengths') class Screen(object): """ Two dimentional buffer of :class:`.Char` instances. Typical usage:: screen = Screen() screen.write_data([ (Token, 'text'), (Token, 'text'), ]) """ def __init__(self, default_char=None, initial_width=0, initial_height=0): if default_char is None: default_char = Char(token=Transparent) self.data_buffer = defaultdict(lambda: defaultdict(lambda: default_char)) #: Position of the cursor. self.cursor_position = Point(y=0, x=0) #: 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_position = None #: 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 #: Mapping of buffer lines to input lines. self.screen_line_to_input_line = {} def write_data(self, data, width=None): """ Write a list of tokens to the screen. When one of the tokens in the token list is ``Token.SetCursorPosition``, this will set the cursor position. :param data: List of Token tuples to write to the buffer. :param width: Width of the line wrap. (Don't wrap when `width` is None.) :returns: A dictionary mapping the character positions of the input data to (x, y) coordinates. """ if width is None: width = 10 ** 100 # A very big number. buffer = self.data_buffer screen_line_to_input_line = self.screen_line_to_input_line x = 0 y = 0 max_allowed_x = x + width index = 0 line_number = 0 requires_line_feed = True indexes_to_pos = {} # Map input positions to (x, y) coordinates. line_lengths = {} # Map line numbers (y) to max_x for this line. set_cursor_position = Token.SetCursorPosition for token, text in data: if token == set_cursor_position: self.cursor_position = Point(y=y, x=x) for char in text: # Line feed. if requires_line_feed: screen_line_to_input_line[y] = line_number requires_line_feed = False char_obj = _CHAR_CACHE[char, token] char_width = char_obj.width # In case there is no more place left at this line, go first to the # following line. (Also in case of double-width characters.) if x + char_width > max_allowed_x and char != '\n': line_lengths[y] = x y += 1 x = 0 # Keep mapping of index to position. indexes_to_pos[index] = (x, y) # Insertion of newline if char == '\n': line_lengths[y] = x y += 1 x = 0 requires_line_feed = True line_number += 1 # Insertion of a 'visible' character. else: buffer_y = buffer[y] buffer_y[x] = char_obj # When we have a double width character, store this byte in the # second cell. So that if this character gets deleted afterwarsd, # the ``output_screen_diff`` will notice that this byte is also # gone and redraw both cells. if char_width > 1: buffer_y[x+1] = Char(six.unichr(0)) # Move position x += char_width index += 1 line_lengths[y] = x self.height = max(self.height, y + 1) self.width = max(self.width, max(line_lengths.values())) return WriteDataResult(indexes_to_pos, line_lengths) def replace_all_tokens(self, token): """ For all the characters in the screen. Set the token to the given `token`. """ b = self.data_buffer for y, row in b.items(): for x, char in row.items(): b[y][x] = _CHAR_CACHE[char.char, token] class WritePosition(object): def __init__(self, xpos, ypos, width, height, extended_height=None): assert height >= 0 assert extended_height is None or extended_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 self.extended_height = extended_height or height def __repr__(self): return '%s(%r, %r, %r, %r, %r)' % ( self.__class__.__name__, self.xpos, self.ypos, self.width, self.height, self.extended_height) prompt_toolkit-0.57/prompt_toolkit/layout/prompt.py0000644000175000017500000000653612600152206024463 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from pygments.token import Token from six import text_type from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER from prompt_toolkit.layout.utils import token_list_to_text from .utils import token_list_len from .processors import Processor, Transformation __all__ = ( 'DefaultPrompt', ) class DefaultPrompt(Processor): """ Default prompt. This one shows the 'arg' and reverse search like Bash/readline normally do. There are two ways to instantiate a ``DefaultPrompt``. For a prompt with a static message, do for instance:: prompt = DefaultPrompt.from_message('prompt> ') For a dynamic prompt, generated from a token list function:: def get_tokens(cli): return [(Token.A, 'text'), (Token.B, 'text2')] prompt = DefaultPrompt(get_tokens) """ def __init__(self, get_tokens): assert callable(get_tokens) self.get_tokens = get_tokens @classmethod def from_message(cls, message='> '): """ Create a default prompt with a static message text. """ assert isinstance(message, text_type) def get_message_tokens(cli): return [(Token.Prompt, message)] return cls(get_message_tokens) def apply_transformation(self, cli, document, tokens): # Get text before cursor. if cli.is_searching: before = _get_isearch_tokens(cli) elif cli.input_processor.arg is not None: before = _get_arg_tokens(cli) else: before = self.get_tokens(cli) # Insert before buffer text. shift_position = token_list_len(before) return Transformation( document=document.insert_before(token_list_to_text(before)), tokens=before + tokens, source_to_display=lambda i: i + shift_position, display_to_source=lambda i: i - shift_position) def has_focus(self, cli): # Obtain focus when the CLI is searching. # Usually, when using this `DefaultPrompt`, we don't have a # `BufferControl` instance that displays the content of the search # buffer. Instead the search text is displayed before the current text. # So, we can still show the cursor here, while it's actually not this # buffer that's focussed. return cli.is_searching def invalidation_hash(self, cli, document): return ( cli.input_processor.arg, cli.is_searching, cli.is_searching and cli.search_state.direction, cli.buffers[SEARCH_BUFFER].text, self.get_tokens(cli), ) def _get_isearch_tokens(cli): def before(): if cli.search_state.direction == IncrementalSearchDirection.BACKWARD: text = 'reverse-i-search' else: text = 'i-search' return [(Token.Prompt.Search, '(%s)`' % text)] def text(): return [(Token.Prompt.Search.Text, cli.buffers[SEARCH_BUFFER].text)] def after(): return [(Token.Prompt.Search, '`: ')] return before() + text() + after() def _get_arg_tokens(cli): """ Tokens for the arg-prompt. """ arg = cli.input_processor.arg return [ (Token.Prompt.Arg, '(arg: '), (Token.Prompt.Arg.Text, str(arg)), (Token.Prompt.Arg, ') '), ] prompt_toolkit-0.57/prompt_toolkit/layout/dimension.py0000644000175000017500000000612212631164350025125 0ustar jonathanjonathan00000000000000""" Layout dimensions are used to give the minimum, maximum and preferred dimensions for containers and controls. """ from __future__ import unicode_literals __all__ = ( 'LayoutDimension', 'sum_layout_dimensions', 'max_layout_dimensions', ) class LayoutDimension(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=1, preferred=None): assert isinstance(weight, int) and weight > 0 # Cannot be a float. self.min_specified = min is not None self.max_specified = max is not None self.preferred_specified = preferred 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 self.min = min self.max = max self.preferred = preferred self.weight = weight # 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:`.LayoutDimension` with an exact size. (min, max and preferred set to ``amount``). """ return cls(min=amount, max=amount, preferred=amount) def __repr__(self): return 'LayoutDimension(min=%r, max=%r, preferred=%r, weight=%r)' % ( self.min, self.max, self.preferred, self.weight) def __add__(self, other): return sum_layout_dimensions([self, other]) def sum_layout_dimensions(dimensions): """ Sum a list of :class:`.LayoutDimension` instances. """ min = sum([d.min for d in dimensions if d.min is not None]) max = sum([d.max for d in dimensions if d.max is not None]) preferred = sum([d.preferred for d in dimensions]) return LayoutDimension(min=min, max=max, preferred=preferred) def max_layout_dimensions(dimensions): """ Take the maximum of a list of :class:`.LayoutDimension` instances. """ min_ = max([d.min for d in dimensions if d.min is not None]) max_ = max([d.max for d in dimensions if d.max is not None]) preferred = max([d.preferred for d in dimensions]) return LayoutDimension(min=min_, max=max_, preferred=preferred) prompt_toolkit-0.57/prompt_toolkit/layout/controls.py0000644000175000017500000007204012642110424025000 0ustar jonathanjonathan00000000000000""" User interface Controls for the layout. """ from __future__ import unicode_literals from pygments.token import Token from abc import ABCMeta, abstractmethod from collections import defaultdict, namedtuple from six import with_metaclass from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.mouse_events import MouseEventTypes from prompt_toolkit.search_state import SearchState from prompt_toolkit.selection import SelectionType from prompt_toolkit.utils import get_cwidth, SimpleLRUCache from .highlighters import Highlighter from .lexers import Lexer, SimpleLexer from .processors import Processor, Transformation from .screen import Screen, Char, Point from .utils import token_list_width, split_lines import time __all__ = ( 'BufferControl', 'FillControl', 'TokenListControl', 'UIControl', ) 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, cli, max_available_width): return None def preferred_height(self, cli, width): return None def has_focus(self, cli): """ Return ``True`` when this user control has the focus. If so, the cursor will be displayed according to the cursor position reported in :meth:`.UIControl.create_screen`. If the created screen has the property ``show_cursor=False``, the cursor will be hidden from the output. """ return False @abstractmethod def create_screen(self, cli, width, height): """ Write the content at this position to the screen. Returns a :class:`.Screen` instance. Optionally, this can also return a (screen, highlighting) tuple, where the `highlighting` is a dictionary of dictionaries. Mapping y->x->Token if this position needs to be highlighted with that Token. """ def mouse_handler(self, cli, 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 cli: `CommandLineInterface` instance. :param mouse_event: `MouseEvent` instance. """ return NotImplemented def move_cursor_down(self, cli): """ Request to move the cursor down. This happens when scrolling down and the cursor is completely at the top. """ def move_cursor_up(self, cli): """ Request to move the cursor up. """ class TokenListControl(UIControl): """ Control that displays a list of (Token, text) tuples. (It's mostly optimized for rather small widgets, like toolbars, menus, etc...) Mouse support: The list of tokens can also contain tuples of three items, looking like: (Token, text, handler). When mouse support is enabled and the user clicks on this token, then the given handler is called. That handler should accept two inputs: (CommandLineInterface, MouseEvent) and it should either handle the event or return `NotImplemented` in case we want the containing Window to handle this event. :param get_tokens: Callable that takes a `CommandLineInterface` instance and returns the list of (Token, text) tuples to be displayed right now. :param default_char: default :class:`.Char` (character and Token) to use for the background when there is more space available than `get_tokens` returns. :param get_default_char: Like `default_char`, but this is a callable that takes a :class:`prompt_toolkit.interface.CommandLineInterface` and returns a :class:`.Char` instance. :param has_focus: `bool` or `CLIFilter`, when this evaluates to `True`, this UI control will take the focus. The cursor will be shown in the upper left corner of this control, unless `get_token` returns a ``Token.SetCursorPosition`` token somewhere in the token list, then the cursor will be shown there. :param wrap_lines: `bool` or `CLIFilter`: Wrap long lines. """ def __init__(self, get_tokens, default_char=None, get_default_char=None, align_right=False, align_center=False, has_focus=False, wrap_lines=True): assert default_char is None or isinstance(default_char, Char) assert get_default_char is None or callable(get_default_char) assert not (default_char and get_default_char) self.align_right = to_cli_filter(align_right) self.align_center = to_cli_filter(align_center) self._has_focus_filter = to_cli_filter(has_focus) self.wrap_lines = to_cli_filter(wrap_lines) self.get_tokens = get_tokens # Construct `get_default_char` callable. if default_char: get_default_char = lambda _: default_char elif not get_default_char: get_default_char = lambda _: Char(' ', Token) self.get_default_char = get_default_char #: Cache for rendered screens. self._screen_lru_cache = SimpleLRUCache(maxsize=18) self._token_lru_cache = SimpleLRUCache(maxsize=1) # Only cache one token list. We don't need the previous item. # Render info for the mouse support. self._tokens = None # The last rendered tokens. self._pos_to_indexes = None # Mapping from mouse positions (x,y) to # positions in the token list. def __repr__(self): return '%s(%r)' % (self.__class__.__name__, self.get_tokens) def _get_tokens_cached(self, cli): """ Get tokens, but only retrieve tokens 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._token_lru_cache.get( cli.render_counter, lambda: self.get_tokens(cli)) def has_focus(self, cli): return self._has_focus_filter(cli) def preferred_width(self, cli, max_available_width): """ Return the preferred width for this control. That is the width of the longest line. """ text = ''.join(t[1] for t in self._get_tokens_cached(cli)) line_lengths = [get_cwidth(l) for l in text.split('\n')] return max(line_lengths) def preferred_height(self, cli, width): screen = self.create_screen(cli, width, None) return screen.height def create_screen(self, cli, width, height): # Get tokens tokens_with_mouse_handlers = self._get_tokens_cached(cli) default_char = self.get_default_char(cli) # Wrap/align right/center parameters. wrap_lines = self.wrap_lines(cli) right = self.align_right(cli) center = self.align_center(cli) def process_line(line): " Center or right align a single line. " used_width = token_list_width(line) padding = width - used_width if center: padding = int(padding / 2) return [(default_char.token, default_char.char * padding)] + line + [(Token, '\n')] if right or center: tokens2 = [] for line in split_lines(tokens_with_mouse_handlers): tokens2.extend(process_line(line)) tokens_with_mouse_handlers = tokens2 # Strip mouse handlers from tokens. tokens = [tuple(item[:2]) for item in tokens_with_mouse_handlers] # Create screen, or take it from the cache. key = (default_char, tokens_with_mouse_handlers, width, wrap_lines, right, center) params = (default_char, tokens, width, wrap_lines, right, center) screen, self._pos_to_indexes = self._screen_lru_cache.get(key, lambda: self._get_screen(*params)) self._tokens = tokens_with_mouse_handlers return screen @classmethod def _get_screen(cls, default_char, tokens, width, wrap_lines, right, center): screen = Screen(default_char, initial_width=width) # Only call write_data when we actually have tokens. # (Otherwise the screen height will go up from 0 to 1 while we don't # want that. -- An empty control should not take up any space.) if tokens: write_data_result = screen.write_data(tokens, width=(width if wrap_lines else None)) indexes_to_pos = write_data_result.indexes_to_pos pos_to_indexes = dict((v, k) for k, v in indexes_to_pos.items()) else: pos_to_indexes = {} return screen, pos_to_indexes @classmethod def static(cls, tokens): def get_static_tokens(cli): return tokens return cls(get_static_tokens) def mouse_handler(self, cli, mouse_event): """ Handle mouse events. (When the token 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 `Window` to handle this particular event.) """ if self._pos_to_indexes: # Find position in the token list. position = mouse_event.position index = self._pos_to_indexes.get((position.x, position.y)) if index is not None: # Find mouse handler for this character. count = 0 for item in self._tokens: count += len(item[1]) if count >= index: if len(item) >= 3: # Handler found. Call it. handler = item[2] handler(cli, mouse_event) return else: break # Otherwise, don't handle here. return NotImplemented class FillControl(UIControl): """ Fill whole control with characters with this token. (Also helpful for debugging.) """ def __init__(self, character=' ', token=Token): self.token = token self.character = character def __repr__(self): return '%s(character=%r, token=%r)' % ( self.__class__.__name__, self.character, self.token) def reset(self): pass def has_focus(self, cli): return False def create_screen(self, cli, width, height): char = Char(self.character, self.token) screen = Screen(char, initial_width=width) return screen class BufferControl(UIControl): """ Control for visualising the content of a `Buffer`. :param input_processors: list of :class:`~prompt_toolkit.layout.processors.Processor`. :param lexer: :class:`~prompt_toolkit.layout.lexers.Lexer` instance for syntax highlighting. :param preview_search: `bool` or `CLIFilter`: Show search while typing. :param get_search_state: Callable that takes a CommandLineInterface and returns the SearchState to be used. (If not CommandLineInterface.search_state.) :param wrap_lines: `bool` or `CLIFilter`: Wrap long lines. :param buffer_name: String representing the name of the buffer to display. :param default_char: :class:`.Char` instance to use to fill the background. This is transparent by default. :param focus_on_click: Focus this buffer when it's click, but not yet focussed. """ def __init__(self, buffer_name=DEFAULT_BUFFER, input_processors=None, highlighters=None, lexer=None, preview_search=False, search_buffer_name=SEARCH_BUFFER, get_search_state=None, wrap_lines=True, menu_position=None, default_char=None, focus_on_click=False): assert input_processors is None or all(isinstance(i, Processor) for i in input_processors) assert highlighters is None or all(isinstance(i, Highlighter) for i in highlighters) assert menu_position is None or callable(menu_position) assert lexer is None or isinstance(lexer, Lexer) assert get_search_state is None or callable(get_search_state) self.preview_search = to_cli_filter(preview_search) self.get_search_state = get_search_state self.wrap_lines = to_cli_filter(wrap_lines) self.focus_on_click = to_cli_filter(focus_on_click) self.input_processors = input_processors or [] self.highlighters = highlighters or [] self.buffer_name = buffer_name self.menu_position = menu_position self.lexer = lexer or SimpleLexer() self.default_char = default_char or Char(token=Token.Transparent) self.search_buffer_name = search_buffer_name #: LRU 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 faily easy way to cache such an expensive operation. self._token_lru_cache = SimpleLRUCache(maxsize=8) #: Keep a similar cache for rendered screens. (when we scroll up/down #: through the screen, or when we change another buffer, we don't want #: to recreate the same screen again.) self._screen_lru_cache = SimpleLRUCache(maxsize=8) #: Highlight Cache. #: When nothing of the buffer content or processors has changed, but #: the highlighting of the selection/search changes, self._highlight_lru_cache = SimpleLRUCache(maxsize=8) self._xy_to_cursor_position = None self._last_click_timestamp = None def _buffer(self, cli): """ The buffer object that contains the 'main' content. """ return cli.buffers[self.buffer_name] def has_focus(self, cli): # This control gets the focussed if the actual `Buffer` instance has the # focus or when any of the `InputProcessor` classes tells us that it # wants the focus. (E.g. in case of a reverse-search, where the actual # search buffer may not be displayed, but the "reverse-i-search" text # should get the focus.) return cli.current_buffer_name == self.buffer_name or \ any(i.has_focus(cli) for i in self.input_processors) def preferred_width(self, cli, max_available_width): # Return the length of the longest line. return max(map(len, self._buffer(cli).document.lines)) def preferred_height(self, cli, width): # Draw content on a screen using this width. Measure the height of the # result. screen, highlighters = self.create_screen(cli, width, None) return screen.height def _get_input_tokens(self, cli, document): """ Tokenize input text for highlighting. Return (tokens, source_to_display, display_to_source) tuple. :param document: The document to be shown. This can be `buffer.document` but could as well be a different one, in case we are searching through the history. (Buffer.document_for_search) """ def get(): # Call lexer. tokens = list(self.lexer.get_tokens(cli, document.text)) # 'Explode' tokens in characters. # (Some input processors -- like search/selection highlighter -- # rely on that each item in the tokens array only contains one # character.) tokens = [(token, c) for token, text in tokens for c in text] # Run all processors over the input. # (They can transform both the tokens and the cursor position.) source_to_display_functions = [] display_to_source_functions = [] d_ = document # Each processor receives the document of the previous one. for p in self.input_processors: transformation = p.apply_transformation(cli, d_, tokens) d_ = transformation.document assert isinstance(transformation, Transformation) tokens = transformation.tokens source_to_display_functions.append(transformation.source_to_display) display_to_source_functions.append(transformation.display_to_source) # Chain cursor transformation (movement) functions. def source_to_display(cursor_position): " Chained source_to_display. " for f in source_to_display_functions: cursor_position = f(cursor_position) return cursor_position def display_to_source(cursor_position): " Chained display_to_source. " for f in reversed(display_to_source_functions): cursor_position = f(cursor_position) return cursor_position return tokens, source_to_display, display_to_source key = ( document.text, # Include invalidation_hashes from all processors. tuple(p.invalidation_hash(cli, document) for p in self.input_processors), ) return self._token_lru_cache.get(key, get) def create_screen(self, cli, width, height): buffer = self._buffer(cli) # 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.) def preview_now(): """ True when we should preview a search. """ return bool(self.preview_search(cli) and cli.buffers[self.search_buffer_name].text) if preview_now(): if self.get_search_state: ss = self.get_search_state(cli) else: ss = cli.search_state document = buffer.document_for_search(SearchState( text=cli.current_buffer.text, direction=ss.direction, ignore_case=ss.ignore_case)) else: document = buffer.document # Wrap. wrap_width = width if self.wrap_lines(cli) else None def _create_screen(): screen = Screen(self.default_char, initial_width=width) # Get tokens # Note: we add the space character at the end, because that's where # the cursor can also be. input_tokens, source_to_display, display_to_source = self._get_input_tokens(cli, document) input_tokens += [(self.default_char.token, ' ')] write_data_result = screen.write_data(input_tokens, width=wrap_width) indexes_to_pos = write_data_result.indexes_to_pos line_lengths = write_data_result.line_lengths pos_to_indexes = dict((v, k) for k, v in indexes_to_pos.items()) def cursor_position_to_xy(cursor_position): """ Turn a cursor position in the buffer into x/y coordinates on the screen. """ cursor_position = min(len(document.text), cursor_position) # First get the real token position by applying all transformations. cursor_position = source_to_display(cursor_position) # Then look up into the table. try: return indexes_to_pos[cursor_position] except KeyError: # This can fail with KeyError, but only if one of the # processors is returning invalid key locations. raise # return 0, 0 def xy_to_cursor_position(x, y): """ Turn x/y screen coordinates back to the original cursor position in the buffer. """ # Look up reverse in table. while x > 0 or y > 0: try: index = pos_to_indexes[x, y] break except KeyError: # No match found -> mouse click outside of region # containing text. Look to the left or up. if x: x -= 1 elif y: y -=1 else: # Nobreak. index = 0 # Transform. return display_to_source(index) return screen, cursor_position_to_xy, xy_to_cursor_position, line_lengths # Build a key for the caching. If any of these parameters changes, we # have to recreate a new screen. key = ( # When the text changes, we obviously have to recreate a new screen. document.text, # When the width changes, line wrapping will be different. # (None when disabled.) wrap_width, # Include invalidation_hashes from all processors. tuple(p.invalidation_hash(cli, document) for p in self.input_processors), ) # Get from cache, or create if this doesn't exist yet. screen, cursor_position_to_xy, self._xy_to_cursor_position, line_lengths = \ self._screen_lru_cache.get(key, _create_screen) x, y = cursor_position_to_xy(document.cursor_position) screen.cursor_position = Point(y=y, x=x) # 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 focussed buffer.) if cli.current_buffer_name == self.buffer_name: menu_position = self.menu_position(cli) if self.menu_position else None if menu_position is not None: assert isinstance(menu_position, int) x, y = cursor_position_to_xy(menu_position) screen.menu_position = Point(y=y, x=x) 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.) x, y = cursor_position_to_xy( min(buffer.cursor_position, buffer.complete_state.original_document.cursor_position)) screen.menu_position = Point(y=y, x=x) else: screen.menu_position = None # Add highlighting. highlight_key = ( key, # Includes everything from the 'key' above. (E.g. when the # document changes, we have to recalculate highlighting.) # Include invalidation_hashes from all highlighters. tuple(h.invalidation_hash(cli, document) for h in self.highlighters) ) highlighting = self._highlight_lru_cache.get(highlight_key, lambda: self._get_highlighting(cli, document, cursor_position_to_xy, line_lengths)) return screen, highlighting def _get_highlighting(self, cli, document, cursor_position_to_xy, line_lengths): """ Return a _HighlightDict for the highlighting. (This is a lazy dict of dicts.) The Window class will apply this for the visible regions. - That way, we don't have to recalculate the screen again for each selection/search change. :param line_lengths: Maps line numbers to the length of these lines. """ def get_row_size(y): " Return the max 'x' value for a given row in the screen. " return max(1, line_lengths.get(y, 0)) # Get list of fragments. row_to_fragments = defaultdict(list) for h in self.highlighters: for fragment in h.get_fragments(cli, document): # Expand fragments. start_column, start_row = cursor_position_to_xy(fragment.start) end_column, end_row = cursor_position_to_xy(fragment.end) token = fragment.token if start_row == end_row: # Single line highlighting. row_to_fragments[start_row].append( _HighlightFragment(start_column, end_column, token)) else: # Multi line highlighting. # (First line.) row_to_fragments[start_row].append( _HighlightFragment(start_column, get_row_size(start_row), token)) # (Middle lines.) for y in range(start_row + 1, end_row): row_to_fragments[y].append(_HighlightFragment(0, get_row_size(y), token)) # (Last line.) row_to_fragments[end_row].append(_HighlightFragment(0, end_column, token)) # Create dict to return. return _HighlightDict(row_to_fragments) def mouse_handler(self, cli, mouse_event): """ Mouse handler for this control. """ buffer = self._buffer(cli) position = mouse_event.position # Focus buffer when clicked. if self.has_focus(cli): if self._xy_to_cursor_position: # Translate coordinates back to the cursor position of the # original input. pos = self._xy_to_cursor_position(position.x, position.y) # Set the cursor position. if pos <= len(buffer.text): if mouse_event.event_type == MouseEventTypes.MOUSE_DOWN: buffer.exit_selection() buffer.cursor_position = pos elif mouse_event.event_type == MouseEventTypes.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 - pos) > 1: buffer.start_selection(selection_type=SelectionType.CHARACTERS) buffer.cursor_position = pos # 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 focussed, but focussing on click events. else: if self.focus_on_click(cli) and mouse_event.event_type == MouseEventTypes.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 # focussed and be handled anyway.) cli.focus(self.buffer_name) else: return NotImplemented def move_cursor_down(self, cli): b = self._buffer(cli) b.cursor_position += b.document.get_cursor_down_position() def move_cursor_up(self, cli): b = self._buffer(cli) b.cursor_position += b.document.get_cursor_up_position() _HighlightFragment = namedtuple('_HighlightFragment', 'start_column end_column token') # End is excluded. class _HighlightDict(dict): """ Helper class to contain the highlighting. Maps 'y' coordinate to 'x' coordinate to Token. :param row_to_fragments: Dictionary that maps row numbers to list of `_HighlightFragment`. """ def __init__(self, row_to_fragments): self.row_to_fragments = row_to_fragments def __missing__(self, key): result = _HighlightDictRow(self.row_to_fragments[key]) self[key] = result return result def __repr__(self): return '_HighlightDict(%r)' % (dict.__repr__(self), ) class _HighlightDictRow(dict): def __init__(self, list_of_fragments): self.list_of_fragments = list_of_fragments def __missing__(self, key): result = None for f in self.list_of_fragments: if f.start_column <= key < f.end_column: # End is excluded. result = f.token break self[key] = result return result prompt_toolkit-0.57/prompt_toolkit/layout/lexers.py0000644000175000017500000000243112605354325024445 0ustar jonathanjonathan00000000000000""" Lexer interface and implementation. Used for syntax highlighting. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass from pygments.token import Token __all__ = ( 'Lexer', 'SimpleLexer', 'PygmentsLexer', ) class Lexer(with_metaclass(ABCMeta, object)): """ Base class for all lexers. """ @abstractmethod def get_tokens(self, cli, text): """ Takes a :class:`~prompt_toolkit.document.Document` and returns a list of tokens. """ return [(Token, text)] class SimpleLexer(Lexer): """ Lexer that returns everything as just one token. """ def __init__(self, default_token=Token): self.default_token = default_token def get_tokens(self, cli, text): return [(self.default_token, text)] class PygmentsLexer(Lexer): """ Lexer that calls a pygments lexer. """ def __init__(self, pygments_lexer_cls): self.pygments_lexer_cls = pygments_lexer_cls # Instantiate the Pygments lexer. self.pygments_lexer = pygments_lexer_cls( stripnl=False, stripall=False, ensurenl=False) def get_tokens(self, cli, text): return self.pygments_lexer.get_tokens(text) prompt_toolkit-0.57/prompt_toolkit/layout/mouse_handlers.py0000644000175000017500000000141412577566373026173 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from itertools import product from collections import defaultdict __all__ = ( 'MouseHandlers', ) class MouseHandlers(object): """ Two dimentional raster of callbacks for mouse events. """ def __init__(self): def dummy_callback(cli, 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-0.57/prompt_toolkit/layout/toolbars.py0000644000175000017500000001535712640634725025007 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from pygments.lexers import BashLexer from pygments.token import Token from ..enums import IncrementalSearchDirection from .processors import BeforeInput from .lexers import PygmentsLexer, SimpleLexer from .dimension import LayoutDimension from .controls import BufferControl, TokenListControl, UIControl from .containers import Window, ConditionalContainer from .utils import token_list_len from .screen import Screen from prompt_toolkit.filters import HasFocus, HasArg, HasCompletions, HasValidationError, HasSearch, Always, IsDone from prompt_toolkit.enums import SEARCH_BUFFER, SYSTEM_BUFFER __all__ = ( 'TokenListToolbar', 'ArgToolbar', 'CompletionsToolbar', 'SearchToolbar', 'SystemToolbar', 'ValidationToolbar', ) class TokenListToolbar(ConditionalContainer): def __init__(self, get_tokens, filter=Always(), **kw): super(TokenListToolbar, self).__init__( content=Window( TokenListControl(get_tokens, **kw), height=LayoutDimension.exact(1)), filter=filter) class SystemToolbarControl(BufferControl): def __init__(self): super(SystemToolbarControl, self).__init__( lexer=PygmentsLexer(BashLexer), buffer_name=SYSTEM_BUFFER, input_processors=[BeforeInput.static('Shell command: ', Token.Toolbar.System)],) class SystemToolbar(ConditionalContainer): def __init__(self): super(SystemToolbar, self).__init__( content=Window( SystemToolbarControl(), height=LayoutDimension.exact(1)), filter=HasFocus(SYSTEM_BUFFER) & ~IsDone()) class ArgToolbarControl(TokenListControl): def __init__(self): def get_tokens(cli): return [ (Token.Toolbar.Arg, 'Repeat: '), (Token.Toolbar.Arg.Text, str(cli.input_processor.arg)), ] super(ArgToolbarControl, self).__init__(get_tokens) class ArgToolbar(ConditionalContainer): def __init__(self): super(ArgToolbar, self).__init__( content=Window( ArgToolbarControl(), height=LayoutDimension.exact(1)), filter=HasArg()) class SearchToolbarControl(BufferControl): """ :param vi_mode: Display '/' and '?' instead of I-search. """ def __init__(self, vi_mode=False): token = Token.Toolbar.Search def get_before_input(cli): if not cli.is_searching: text = '' elif cli.search_state.direction == IncrementalSearchDirection.BACKWARD: text = ('?' if vi_mode else 'I-search backward: ') else: text = ('/' if vi_mode else 'I-search: ') return [(token, text)] super(SearchToolbarControl, self).__init__( buffer_name=SEARCH_BUFFER, input_processors=[BeforeInput(get_before_input)], lexer=SimpleLexer(default_token=token.Text)) class SearchToolbar(ConditionalContainer): def __init__(self, vi_mode=False): super(SearchToolbar, self).__init__( content=Window( SearchToolbarControl(vi_mode=vi_mode), height=LayoutDimension.exact(1)), filter=HasSearch() & ~IsDone()) class CompletionsToolbarControl(UIControl): token = Token.Toolbar.Completions def create_screen(self, cli, width, height): complete_state = cli.current_buffer.complete_state if complete_state: completions = complete_state.current_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. tokens = [] for i, c in enumerate(completions): # When there is no more place for the next completion if token_list_len(tokens) + len(c.display) >= content_width: # If the current one was not yet displayed, page to the next sequence. if i <= (index or 0): tokens = [] cut_left = True # If the current one is visible, stop here. else: cut_right = True break tokens.append((self.token.Completion.Current if i == index else self.token.Completion, c.display)) tokens.append((self.token, ' ')) # Extend/strip until the content width. tokens.append((self.token, ' ' * (content_width - token_list_len(tokens)))) tokens = tokens[:content_width] # Return tokens all_tokens = [ (self.token, ' '), (self.token.Arrow, '<' if cut_left else ' '), (self.token, ' '), ] + tokens + [ (self.token, ' '), (self.token.Arrow, '>' if cut_right else ' '), (self.token, ' '), ] else: all_tokens = [] screen = Screen(initial_width=width) screen.write_data(all_tokens, width) return screen class CompletionsToolbar(ConditionalContainer): def __init__(self, extra_filter=Always()): super(CompletionsToolbar, self).__init__( content=Window( CompletionsToolbarControl(), height=LayoutDimension.exact(1)), filter=HasCompletions() & ~IsDone() & extra_filter) class ValidationToolbarControl(TokenListControl): def __init__(self, show_position=False): token = Token.Toolbar.Validation def get_tokens(cli): buffer = cli.current_buffer if buffer.validation_error: row, column = buffer.document.translate_index_to_position( buffer.validation_error.cursor_position) if show_position: text = '%s (line=%s column=%s)' % ( buffer.validation_error.message, row + 1, column + 1) else: text = buffer.validation_error.message return [(token, text)] else: return [] super(ValidationToolbarControl, self).__init__(get_tokens) class ValidationToolbar(ConditionalContainer): def __init__(self, show_position=False): super(ValidationToolbar, self).__init__( content=Window( ValidationToolbarControl(show_position=show_position), height=LayoutDimension.exact(1)), filter=HasValidationError() & ~IsDone()) prompt_toolkit-0.57/prompt_toolkit/layout/highlighters.py0000644000175000017500000001630412642065332025626 0ustar jonathanjonathan00000000000000""" Highlighters for usage in a BufferControl. Highlighters are very similar to processors, but they are applied after the BufferControl created a screen instance. (Instead of right before creating the screen.) Highlighters can't change the content of the screen, but they can mark regions (start_pos, end_pos) as highlighted, using a certain Token. When possible, it's adviced to use a Highlighter instead of a Processor, because most of the highlighting code is applied only to the visible region of the screen. (The Window class will apply the highlighting to the visible region.) """ from __future__ import unicode_literals from pygments.token import Token from abc import ABCMeta, abstractmethod from six import with_metaclass from prompt_toolkit.document import Document from prompt_toolkit.enums import SEARCH_BUFFER from prompt_toolkit.filters import to_cli_filter __all__ = ( 'Fragment', 'SelectionHighlighter', 'SearchHighlighter', 'MatchingBracketHighlighter', 'ConditionalHighlighter', ) class Fragment(object): """ Highlight fragment. :param start: (int) Cursor start position. :param end: (int) Cursor end position. :param token: Pygments Token. """ def __init__(self, start, end, token): self.start = start self.end = end self.token = token def __repr__(self): return 'Fragment(%r, %r, %r)' % (self.start, self.end, self.token) class Highlighter(with_metaclass(ABCMeta, object)): @abstractmethod def get_fragments(self, cli, document): """ Return a list of :class:`.Fragment` instances. (This can be a generator as well.) """ return [] class SelectionHighlighter(Highlighter): """ Highlight the selection. """ def get_fragments(self, cli, document): for from_, to in document.selection_ranges(): yield Fragment(from_, to + 1, Token.SelectedText) def invalidation_hash(self, cli, document): # When the selection changes, highlighting will be different. return (document.selection and ( document.cursor_position, document.selection.original_cursor_position, document.selection.type)) class SearchHighlighter(Highlighter): """ Highlight search matches in the document. :param preview_search: A Filter; when active it indicates that we take the search text in real time while the user is typing, instead of the last active search state. :param get_search_state: (Optional) Callable that takes a CommandLineInterface and returns the SearchState to be used for the highlighting. """ def __init__(self, preview_search=False, search_buffer_name=SEARCH_BUFFER, get_search_state=None): self.preview_search = to_cli_filter(preview_search) self.search_buffer_name = search_buffer_name self.get_search_state = get_search_state def _get_search_text(self, cli): """ The text we are searching for. """ # When the search buffer has focus, take that text. if self.preview_search(cli) and cli.buffers[self.search_buffer_name].text: return cli.buffers[self.search_buffer_name].text # Otherwise, take the text of the last active search. elif self.get_search_state: return self.get_search_state(cli).text else: return cli.search_state.text def get_fragments(self, cli, document): search_text = self._get_search_text(cli) search_text_length = len(search_text) ignore_case = cli.is_ignoring_case if search_text and not cli.is_returning: for index in document.find_all(search_text, ignore_case=ignore_case): if index <= document.cursor_position < index + search_text_length: token = Token.SearchMatch.Current else: token = Token.SearchMatch yield Fragment(index, index + len(search_text), token) def invalidation_hash(self, cli, document): search_text = self._get_search_text(cli) # When the search state changes, highlighting will be different. return ( search_text, cli.is_returning, # When we search for text, and the cursor position changes. The # processor has to be applied every time again, because the current # match is highlighted in another color. (search_text and document.cursor_position), ) class ConditionalHighlighter(Highlighter): """ Highlighter that applies another highlighter, according to a certain condition. :param highlighter: :class:`.Highlighter` instance. :param filter: :class:`~prompt_toolkit.filters.CLIFilter` instance. """ def __init__(self, highlighter, filter): assert isinstance(highlighter, Highlighter) self.highlighter = highlighter self.filter = to_cli_filter(filter) def get_fragments(self, cli, document): if self.filter(cli): return self.highlighter.get_fragments(cli, document) else: return [] def invalidation_hash(self, cli, document): # When enabled, use the hash of the highlighter. Otherwise, just use # False. if self.filter(cli): return (True, self.highlighter.invalidation_hash(cli, document)) else: return False class MatchingBracketHighlighter(Highlighter): """ When the cursor is on or right after a bracket, it highlights the matching bracket. """ _closing_braces = '])}>' def __init__(self, chars='[](){}<>'): self.chars = chars def get_fragments(self, cli, document): result = [] def replace_token(pos): """ Replace token in list of tokens. """ result.append(Fragment(pos, pos + 1, Token.MatchingBracket)) def apply_for_document(document): """ Find and replace matching tokens. """ if document.current_char in self.chars: pos = document.matching_bracket_position if pos: replace_token(document.cursor_position) replace_token(document.cursor_position + pos) return True # Apply for character below cursor. applied = apply_for_document(document) # Otherwise, apply for character before cursor. if (not applied and document.cursor_position > 0 and document.char_before_cursor in self._closing_braces): apply_for_document(Document(document.text, document.cursor_position - 1)) return result def invalidation_hash(self, cli, document): on_brace = document.current_char in self.chars after_brace = document.char_before_cursor in self.chars if on_brace: return (True, document.cursor_position) elif after_brace and document.char_before_cursor in self._closing_braces: return (True, document.cursor_position - 1) else: # Don't include the cursor position in the hash if we are not *on* # a brace. We don't have to rerender the output, because it will be # the same anyway. return False prompt_toolkit-0.57/prompt_toolkit/layout/margins.py0000644000175000017500000001412712623240275024606 0ustar jonathanjonathan00000000000000""" Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. """ from __future__ import unicode_literals from six import with_metaclass from abc import ABCMeta, abstractmethod from prompt_toolkit.filters import to_cli_filter from pygments.token import Token __all__ = ( 'Margin', 'NumberredMargin', 'ScrollbarMargin', 'ConditionalMargin', ) class Margin(with_metaclass(ABCMeta, object)): """ Base interface for a margin. """ @abstractmethod def get_width(self, cli): """ Return the width that this margin is going to consume. """ return 0 @abstractmethod def create_margin(self, cli, window_render_info, width, height): """ Creates a margin. This should return a list of (Token, 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 NumberredMargin(Margin): """ Margin that displays the line numbers. :param buffer_name: The name of the buffer. This is recommended if the margin is used together with a :class:`~prompt_toolkit.layout.controls.BufferControl` inside a :class:`~prompt_toolkit.layout.containers.Window`. That way, we can predict the width of this margin (the amount of decimals) according to the number of lines in the buffer. :param width: If no buffer name is given, width can be used to set a fixed width. :param relative: Number relative to the cursor position. Similar to the Vi 'relativenumber' option. """ def __init__(self, buffer_name=None, width=None, relative=False): assert buffer_name or width self.buffer_name = buffer_name self.width = width self.relative = to_cli_filter(relative) def get_width(self, cli): if self.width is not None: # Fixed width. return self.width else: # Width determined by the amount of lines in the buffer. document = cli.buffers[self.buffer_name].document return max(3, len('%s' % document.line_count) + 1) def create_margin(self, cli, window_render_info, width, height): visible_line_to_input_line = window_render_info.visible_line_to_input_line relative = self.relative(cli) token = Token.LineNumber token_current = Token.LineNumber.Current # Get current line number. if self.buffer_name: # (BufferControl will only have a cursor position when the buffer # has the focus, so this is a better way to know the current line.) document = cli.buffers[self.buffer_name].document current_lineno = document.cursor_position_row else: current_lineno = visible_line_to_input_line.get(window_render_info.cursor_position.y) or 0 # Construct margin. result = [] for y in range(window_render_info.window_height): line_number = visible_line_to_input_line.get(y) if line_number is not None: if line_number == current_lineno: # Current line. if relative: # Left align current number in relative mode. result.append((token_current, '%i' % (line_number + 1))) else: result.append((token_current, ('%i ' % (line_number + 1)).rjust(width))) else: # Other lines. if relative: line_number = abs(line_number - current_lineno) - 1 result.append((token, ('%i ' % (line_number + 1)).rjust(width))) result.append((Token, '\n')) 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_cli_filter(filter) def get_width(self, cli): if self.filter(cli): return self.margin.get_width(cli) else: return 0 def create_margin(self, cli, window_render_info, width, height): if width and self.filter(cli): return self.margin.create_margin(cli, window_render_info, width, height) else: return [] class ScrollbarMargin(Margin): """ Margin displaying a scrollbar. """ def get_width(self, cli): return 1 def create_margin(self, cli, window_render_info, width, height): total_height = window_render_info.content_height items_per_row = float(total_height) / min(total_height, window_render_info.window_height - 2) index = window_render_info.vertical_scroll visible_lines = set(range(index, index + window_render_info.window_height)) def is_scroll_button(row): " True if we should display a button on this row. " current_row_middle = int((row + .5) * items_per_row) return current_row_middle in visible_lines # Generate tokens. result = [ (Token.Scrollbar.Arrow, '\u25b2'), # Up arrow. (Token.Scrollbar, '\n') ] for i in range(window_render_info.window_height - 2): if is_scroll_button(i): result.append((Token.Scrollbar.Button, ' ')) else: result.append((Token.Scrollbar, ' ')) result.append((Token, '\n')) result.append((Token.Scrollbar.Arrow, '\u25bc')) # Down arrow return result prompt_toolkit-0.57/prompt_toolkit/layout/menus.py0000644000175000017500000004747112607073260024305 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from six.moves import zip_longest from prompt_toolkit.filters import HasCompletions, IsDone, Always, Condition, to_cli_filter from prompt_toolkit.mouse_events import MouseEventTypes from prompt_toolkit.reactive import Integer from prompt_toolkit.utils import get_cwidth from pygments.token import Token from .containers import Window, HSplit, ConditionalContainer from .controls import UIControl from .dimension import LayoutDimension from .screen import Screen import math __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. """ def __init__(self, scroll_offset=0): assert isinstance(scroll_offset, Integer) self.token = Token.Menu.Completions self.scroll_offset = scroll_offset self.scroll = 0 def reset(self): self.scroll = 0 def has_focus(self, cli): return False def preferred_width(self, cli, max_available_width): complete_state = cli.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 + 1 else: return 0 def preferred_height(self, cli, width): complete_state = cli.current_buffer.complete_state if complete_state: return len(complete_state.current_completions) else: return 0 def create_screen(self, cli, width, height): """ Write the menu to the screen object. """ screen = Screen() complete_state = cli.current_buffer.complete_state if complete_state: completions = complete_state.current_completions index = complete_state.complete_index # Can be None! # Calculate width of completions menu. menu_width = self._get_menu_width(width - 1, complete_state) menu_meta_width = self._get_menu_meta_width(width - 1 - menu_width, complete_state) show_meta = self._show_meta(complete_state) if menu_width + menu_meta_width + 1 < width: menu_width += width - (menu_width + menu_meta_width + 1) # Update the scroll offset. scroll_offset = min(height // 2, int(self.scroll_offset)) self.scroll = min(self.scroll, (index or 0) - scroll_offset) self.scroll = max(self.scroll, (index or 0) - height + 1 + scroll_offset) self.scroll = min(self.scroll, len(completions) - height) # Never go out of range. self.scroll = max(self.scroll, 0) # Never go negative. # Decide which slice of completions to show. slice_from = self.scroll slice_to = min(slice_from + height, len(completions)) # Create a function which decides at which positions the scroll button should be shown. items_per_row = float(len(completions)) / min(len(completions), height) def is_scroll_button(row): items_on_this_row_from = row * items_per_row items_on_this_row_to = (row + 1) * items_per_row return items_on_this_row_from <= (index or 0) < items_on_this_row_to # Write completions to screen. tokens = [] for i, c in enumerate(completions[slice_from:slice_to]): is_current_completion = (i + slice_from == index) if is_scroll_button(i): button_token = self.token.ProgressButton else: button_token = self.token.ProgressBar if tokens: tokens += [(Token, '\n')] tokens += (self._get_menu_item_tokens(c, is_current_completion, menu_width) + (self._get_menu_item_meta_tokens(c, is_current_completion, menu_meta_width) if show_meta else []) + [(button_token, ' '), ]) screen.write_data(tokens, width) return screen def _show_meta(self, complete_state): """ Return ``True`` if we need to show a column with meta information. """ return any(c.display_meta for c in complete_state.current_completions) def _get_menu_width(self, max_width, complete_state): """ Return the width of the main column. """ return min(max_width, max(get_cwidth(c.display) for c in complete_state.current_completions) + 2) def _get_menu_meta_width(self, max_width, complete_state): """ Return the width of the meta column. """ if self._show_meta(complete_state): return min(max_width, max(get_cwidth(c.display_meta) for c in complete_state.current_completions) + 2) else: return 0 def _get_menu_item_tokens(self, completion, is_current_completion, width): if is_current_completion: token = self.token.Completion.Current else: token = self.token.Completion text, tw = _trim_text(completion.display, width - 2) padding = ' ' * (width - 2 - tw) return [(token, ' %s%s ' % (text, padding))] def _get_menu_item_meta_tokens(self, completion, is_current_completion, width): if is_current_completion: token = self.token.Meta.Current else: token = self.token.Meta text, tw = _trim_text(completion.display_meta, width - 2) padding = ' ' * (width - 2 - tw) return [(token, ' %s%s ' % (text, padding))] def mouse_handler(self, cli, mouse_event): """ Handle mouse events: clicking and scrolling. """ b = cli.current_buffer if mouse_event.event_type == MouseEventTypes.MOUSE_UP: # Select completion. b.go_to_completion(self.scroll + mouse_event.position.y) b.complete_state = None elif mouse_event.event_type == MouseEventTypes.SCROLL_DOWN: # Scroll up. b.complete_next(count=3, disable_wrap_around=True) self.scroll += 3 elif mouse_event.event_type == MouseEventTypes.SCROLL_UP: # Scroll down. b.complete_previous(count=3, disable_wrap_around=True) self.scroll -= 3 def _trim_text(text, max_width): """ Trim the text to `max_width`, append dots when the text is too long. Returns (text, width) tuple. """ width = get_cwidth(text) # When the text is too wide, trim it. if width > max_width: # When there are no double width characters, just use slice operation. if len(text) == width: trimmed_text = (text[:max(1, max_width-3)] + '...')[:max_width] return trimmed_text, len(trimmed_text) # Otherwise, loop until we have the desired width. (Rather # inefficient, but ok for now.) else: trimmed_text = '' for c in text: if get_cwidth(trimmed_text + c) <= max_width - 3: trimmed_text += c trimmed_text += '...' return (trimmed_text, get_cwidth(trimmed_text)) else: return text, width class CompletionsMenu(ConditionalContainer): def __init__(self, max_height=None, scroll_offset=0, extra_filter=Always()): super(CompletionsMenu, self).__init__( content=Window( content=CompletionsMenuControl(scroll_offset=scroll_offset), width=LayoutDimension(min=8), height=LayoutDimension(min=1, max=max_height)), # Show when there are completions but not at the point we are # returning the input. filter=HasCompletions() & ~IsDone() & 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 langer than one, in 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.token = Token.Menu.Completions 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, cli): return False def preferred_width(self, cli, max_available_width): """ Preferred width: prefer to use at least min_rows, but otherwise as much as possible horizontally. """ complete_state = cli.current_buffer.complete_state column_width = self._get_column_width(complete_state) result = int(column_width * math.ceil(len(complete_state.current_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, cli, width): """ Preferred height: as much as needed in order to display all the completions. """ complete_state = cli.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.current_completions) / float(column_count))) def create_screen(self, cli, width, height): """ Write the menu to the screen object. """ complete_state = cli.current_buffer.complete_state column_width = self._get_column_width(complete_state) self._render_pos_to_completion = {} screen = Screen() 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.current_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. tokens = [] for row_index, row in enumerate(rows_): middle_row = row_index == len(rows_) // 2 # Draw left arrow if we have hidden completions on the left. if render_left_arrow: tokens += [(self.token.ProgressBar, '<' if middle_row else ' ')] # Draw row content. for column_index, c in enumerate(row[self.scroll:][:visible_columns]): if c is not None: tokens += self._get_menu_item_tokens(c, is_current_completion(c), column_width) # 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: tokens += [(self.token.Completion, ' ' * column_width)] # Draw trailing padding. (_get_menu_item_tokens only returns padding on the left.) tokens += [(self.token.Completion, ' ')] # Draw right arrow if we have hidden completions on the right. if render_right_arrow: tokens += [(self.token.ProgressBar, '>' if middle_row else ' ')] # Newline. tokens += [(self.token.ProgressBar, '\n')] screen.write_data(tokens, width) 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 return screen def _get_column_width(self, complete_state): """ Return the width of each column. """ return max(get_cwidth(c.display) for c in complete_state.current_completions) + 1 def _get_menu_item_tokens(self, completion, is_current_completion, width): if is_current_completion: token = self.token.Completion.Current else: token = self.token.Completion text, tw = _trim_text(completion.display, width) padding = ' ' * (width - tw - 1) return [(token, ' %s%s' % (text, padding))] def mouse_handler(self, cli, mouse_event): """ Handle scoll and click events. """ b = cli.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 == MouseEventTypes.SCROLL_DOWN: scroll_right() elif mouse_event.event_type == MouseEventTypes.SCROLL_UP: scroll_left() elif mouse_event.event_type == MouseEventTypes.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) class MultiColumnCompletionsMenu(HSplit): """ Container that displays the completions in several columns. When `show_meta` (a :class:`~prompt_toolkit.filters.CLIFilter`) 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): show_meta = to_cli_filter(show_meta) extra_filter = to_cli_filter(extra_filter) # Display filter: show when there are completions but not at the point # we are returning the input. full_filter = HasCompletions() & ~IsDone() & extra_filter any_completion_has_meta = Condition(lambda cli: any(c.display_meta for c in cli.current_buffer.complete_state.current_completions)) # Create child windows. completions_window = ConditionalContainer( content=Window( content=MultiColumnCompletionMenuControl( min_rows=min_rows, suggested_max_column_width=suggested_max_column_width), width=LayoutDimension(min=8), height=LayoutDimension(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 ]) class _SelectedCompletionMetaControl(UIControl): """ Control that shows the meta information of the selected token. """ def preferred_width(self, cli, 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.) """ if cli.current_buffer.complete_state: state = cli.current_buffer.complete_state return 2 + max(get_cwidth(c.display_meta) for c in state.current_completions) else: return 0 def preferred_height(self, cli, width): return 1 def create_screen(self, cli, width, height): screen = Screen() screen.write_data(self._get_tokens(cli), width) return screen def _get_tokens(self, cli): token = Token.Menu.Completions.MultiColumnMeta state = cli.current_buffer.complete_state if state and state.current_completion and state.current_completion.display_meta: return [(token, ' %s ' % state.current_completion.display_meta)] return [] prompt_toolkit-0.57/prompt_toolkit/layout/containers.py0000644000175000017500000013525612641116033025314 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 collections import defaultdict from pygments.token import Token from six import with_metaclass from .screen import Point, WritePosition, Char from .dimension import LayoutDimension, sum_layout_dimensions, max_layout_dimensions from .controls import UIControl, TokenListControl from .margins import Margin from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.mouse_events import MouseEvent, MouseEventTypes from prompt_toolkit.utils import SimpleLRUCache, take_using_weights __all__ = ( 'Container', 'HSplit', 'VSplit', 'FloatContainer', 'Float', 'Window', 'WindowRenderInfo', 'ConditionalContainer', 'ScrollOffsets' ) Transparent = Token.Transparent 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, cli, max_available_width): """ Return a :class:`~prompt_toolkit.layout.dimension.LayoutDimension` that represents the desired width for this container. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. """ @abstractmethod def preferred_height(self, cli, width): """ Return a :class:`~prompt_toolkit.layout.dimension.LayoutDimension` that represents the desired height for this container. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. """ @abstractmethod def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ Write the actual content to the screen. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface`. :param screen: :class:`~prompt_toolkit.layout.screen.Screen` :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`. """ @abstractmethod def walk(self, cli): """ Walk through all the layout nodes (and their children) and yield them. """ def _window_too_small(): " Create a `Window` that displays the 'Window too small' text. " return Window(TokenListControl.static( [(Token.WindowTooSmall, ' Window too small... ')])) class HSplit(Container): """ Several layouts, one stacked above/under the other. :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 get_dimensions: (`None` or a callable that takes a `CommandLineInterface` and returns a list of `LayoutDimension` instances.) By default the dimensions are taken from the children and divided by the available space. However, when `get_dimensions` is specified, this is taken instead. :param report_dimensions_callback: When rendering, this function is called with the `CommandLineInterface` and the list of used dimensions. (As a list of integers.) """ def __init__(self, children, window_too_small=None, get_dimensions=None, report_dimensions_callback=None): assert all(isinstance(c, Container) for c in children) assert window_too_small is None or isinstance(window_too_small, Container) assert get_dimensions is None or callable(get_dimensions) assert report_dimensions_callback is None or callable(report_dimensions_callback) self.children = children self.window_too_small = window_too_small or _window_too_small() self.get_dimensions = get_dimensions self.report_dimensions_callback = report_dimensions_callback def preferred_width(self, cli, max_available_width): if self.children: dimensions = [c.preferred_width(cli, max_available_width) for c in self.children] return max_layout_dimensions(dimensions) else: return LayoutDimension(0) def preferred_height(self, cli, width): dimensions = [c.preferred_height(cli, width) for c in self.children] return sum_layout_dimensions(dimensions) def reset(self): for c in self.children: c.reset() def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ 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_heigths(cli, write_position) if self.report_dimensions_callback: self.report_dimensions_callback(cli, sizes) if sizes is None: self.window_too_small.write_to_screen( cli, screen, mouse_handlers, write_position) else: # Draw child panes. ypos = write_position.ypos xpos = write_position.xpos width = write_position.width for s, c in zip(sizes, self.children): c.write_to_screen(cli, screen, mouse_handlers, WritePosition(xpos, ypos, width, s)) ypos += s def _divide_heigths(self, cli, write_position): """ Return the heights for all rows. Or None when there is not enough space. """ if not self.children: return [] # Calculate heights. given_dimensions = self.get_dimensions(cli) if self.get_dimensions else None def get_dimension_for_child(c, index): if given_dimensions and given_dimensions[index] is not None: return given_dimensions[index] else: return c.preferred_height(cli, write_position.width) dimensions = [get_dimension_for_child(c, index) for index, c in enumerate(self.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 > write_position.extended_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) while sum(sizes) < min(write_position.extended_height, sum_dimensions.preferred): # Increase until we meet at least the 'preferred' size. if sizes[i] < dimensions[i].preferred: sizes[i] += 1 i = next(child_generator) if not any([cli.is_returning, cli.is_exiting, cli.is_aborting]): while sum(sizes) < min(write_position.height, sum_dimensions.max): # Increase until we use all the available space. (or until "max") if sizes[i] < dimensions[i].max: sizes[i] += 1 i = next(child_generator) return sizes def walk(self, cli): """ Walk through children. """ yield self for c in self.children: for i in c.walk(cli): yield i class VSplit(Container): """ Several layouts, one stacked left/right of the other. :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 get_dimensions: (`None` or a callable that takes a `CommandLineInterface` and returns a list of `LayoutDimension` instances.) By default the dimensions are taken from the children and divided by the available space. However, when `get_dimensions` is specified, this is taken instead. :param report_dimensions_callback: When rendering, this function is called with the `CommandLineInterface` and the list of used dimensions. (As a list of integers.) """ def __init__(self, children, window_too_small=None, get_dimensions=None, report_dimensions_callback=None): assert all(isinstance(c, Container) for c in children) assert window_too_small is None or isinstance(window_too_small, Container) assert get_dimensions is None or callable(get_dimensions) assert report_dimensions_callback is None or callable(report_dimensions_callback) self.children = children self.window_too_small = window_too_small or _window_too_small() self.get_dimensions = get_dimensions self.report_dimensions_callback = report_dimensions_callback def preferred_width(self, cli, max_available_width): dimensions = [c.preferred_width(cli, max_available_width) for c in self.children] return sum_layout_dimensions(dimensions) def preferred_height(self, cli, width): sizes = self._divide_widths(cli, width) if sizes is None: return LayoutDimension() else: dimensions = [c.preferred_height(cli, s) for s, c in zip(sizes, self.children)] return max_layout_dimensions(dimensions) def reset(self): for c in self.children: c.reset() def _divide_widths(self, cli, width): """ Return the widths for all columns. Or None when there is not enough space. """ if not self.children: return [] # Calculate widths. given_dimensions = self.get_dimensions(cli) if self.get_dimensions else None def get_dimension_for_child(c, index): if given_dimensions and given_dimensions[index] is not None: return given_dimensions[index] else: return c.preferred_width(cli, width) dimensions = [get_dimension_for_child(c, index) for index, c in enumerate(self.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 > width: 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) while sum(sizes) < min(width, sum_dimensions.preferred): # Increase until we meet at least the 'preferred' size. if sizes[i] < dimensions[i].preferred: sizes[i] += 1 i = next(child_generator) while sum(sizes) < min(width, sum_dimensions.max): # Increase until we use all the available space. if sizes[i] < dimensions[i].max: sizes[i] += 1 i = next(child_generator) return sizes def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ 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 sizes = self._divide_widths(cli, write_position.width) if self.report_dimensions_callback: self.report_dimensions_callback(cli, sizes) # If there is not enough space. if sizes is None: self.window_too_small.write_to_screen( cli, screen, mouse_handlers, write_position) return # Calculate heights, take the largest possible, but not larger than write_position.extended_height. heights = [child.preferred_height(cli, width).preferred for width, child in zip(sizes, self.children)] height = max(write_position.height, min(write_position.extended_height, max(heights))) # Draw child panes. ypos = write_position.ypos xpos = write_position.xpos for s, c in zip(sizes, self.children): c.write_to_screen(cli, screen, mouse_handlers, WritePosition(xpos, ypos, s, height)) xpos += s def walk(self, cli): """ Walk through children. """ yield self for c in self.children: for i in c.walk(cli): yield i 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(...)) ]) """ def __init__(self, content, floats): assert isinstance(content, Container) assert all(isinstance(f, Float) for f in floats) self.content = content self.floats = floats def reset(self): self.content.reset() for f in self.floats: f.content.reset() def preferred_width(self, cli, write_position): return self.content.preferred_width(cli, write_position) def preferred_height(self, cli, width): """ 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(cli, width) def write_to_screen(self, cli, screen, mouse_handlers, write_position): self.content.write_to_screen(cli, screen, mouse_handlers, write_position) for fl in self.floats: # 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. cursor_position = screen.menu_position or screen.cursor_position cursor_position = Point(x=cursor_position.x - write_position.xpos, y=cursor_position.y - write_position.ypos) fl_width = fl.get_width(cli) fl_height = fl.get_height(cli) # 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(cli, 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(cli, 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 + 1 height = fl_height if height is None: height = fl.content.preferred_height(cli, width).preferred # Reduce height if not enough space. (We can use the # extended_height when the content requires it.) if height > write_position.extended_height - ypos: if write_position.extended_height - ypos + 1 >= ypos: # When the space below the cursor is more than # the space above, just reduce the height. height = write_position.extended_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(cli, width).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) fl.content.write_to_screen(cli, screen, mouse_handlers, wp) def walk(self, cli): """ Walk through children. """ yield self for i in self.content.walk(cli): yield i for f in self.floats: for i in f.content.walk(cli): yield i class Float(object): """ Float for use in a :class:`.FloatContainer`. :param content: :class:`.Container` instance. """ def __init__(self, top=None, right=None, bottom=None, left=None, width=None, height=None, get_width=None, get_height=None, xcursor=False, ycursor=False, content=None): assert isinstance(content, Container) assert width is None or get_width is None assert height is None or get_height is None self.left = left self.right = right self.top = top self.bottom = bottom self._width = width self._height = height self._get_width = get_width self._get_height = get_height self.xcursor = xcursor self.ycursor = ycursor self.content = content def get_width(self, cli): if self._width: return self._width if self._get_width: return self._get_width(cli) def get_height(self, cli): if self._height: return self._height if self._get_height: return self._get_height(cli) 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 original_screen: The original full screen instance that contains the whole input, without clipping. (temp_screen) :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. :param height: The height that was used for the rendering. :param cursor_position: `Point` instance. Where the cursor is currently shown, relative to the window. """ def __init__(self, original_screen, horizontal_scroll, vertical_scroll, window_width, window_height, cursor_position, configured_scroll_offsets, applied_scroll_offsets): self.original_screen = original_screen self.vertical_scroll = vertical_scroll self.window_width = window_width self.window_height = window_height self.cursor_position = cursor_position self.configured_scroll_offsets = configured_scroll_offsets self.applied_scroll_offsets = applied_scroll_offsets @property def input_line_to_screen_line(self): """ Return a dictionary mapping the line numbers of the screen to the one of the input buffer. """ return dict((v, k) for k, v in self.original_screen.screen_line_to_input_line.items()) @property def screen_line_to_input_line(self): """ Return the dictionary mapping the line numbers of the input buffer to the lines of the screen. """ return self.original_screen.screen_line_to_input_line @property def visible_line_to_input_line(self): """ Return a dictionary mapping the visible rows to the line numbers of the input. """ return dict((k - self.vertical_scroll, v) for k, v in self.original_screen.screen_line_to_input_line.items()) 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. """ # Note that we can't just do vertical_scroll+height because some input # lines could be wrapped and span several lines in the screen. screen = self.original_screen height = self.window_height start = self.vertical_scroll if after_scroll_offset: start += self.applied_scroll_offsets.top for y in range(start, self.vertical_scroll + height): if y in screen.screen_line_to_input_line: return screen.screen_line_to_input_line[y] return 0 def last_visible_line(self, before_scroll_offset=False): """ Like `first_visible_line`, but for the last visible line. """ screen = self.original_screen height = self.window_height start = self.vertical_scroll + height - 1 if before_scroll_offset: start -= self.applied_scroll_offsets.bottom for y in range(start, self.vertical_scroll, -1): if y in screen.screen_line_to_input_line: return screen.screen_line_to_input_line[y] return 0 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.original_screen.height @property def full_height_visible(self): """ True when the full height is visible (There is no vertical scroll.) """ return self.window_height >= self.original_screen.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.vertical_scroll >= \ self.original_screen.height - self.window_height @property def vertical_scroll_percentage(self): """ Vertical scroll as a percentage. (0 means: the top is visible, 100 means: the bottom is visible.) """ return (100 * self.vertical_scroll // (self.original_screen.height - self.window_height)) 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): self.top = top self.bottom = bottom self.left = left self.right = right def __repr__(self): return 'ScrollOffsets(top=%r, bottom=%r, left=%r, right=%r)' % ( self.top, self.bottom, self.left, self.right) class Window(Container): """ Container that holds a control. :param content: :class:`~prompt_toolkit.layout.controls.UIControl` instance. :param width: :class:`~prompt_toolkit.layout.dimension.LayoutDimension` instance. :param height: :class:`~prompt_toolkit.layout.dimension.LayoutDimension` instance. :param get_width: callable which takes a `CommandLineInterface` and returns a `LayoutDimension`. :param get_height: callable which takes a `CommandLineInterface` and returns a `LayoutDimension`. :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 left_margins: A list of :class:`~prompt_toolkit.layout.margins.Margin` instance to be displayed on the left. For instance: :class:`~prompt_toolkit.layout.margins.NumberredMargin` 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:`~prompt_toolkit.filters.CLIFilter` 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 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:`~prompt_toolkit.filters.CLIFilter` instance. When True, never display the cursor, even when the user control specifies a cursor position. """ def __init__(self, content, width=None, height=None, get_width=None, get_height=None, dont_extend_width=False, dont_extend_height=False, left_margins=None, right_margins=None, scroll_offsets=None, allow_scroll_beyond_bottom=False, get_vertical_scroll=None, get_horizontal_scroll=None, always_hide_cursor=False): assert isinstance(content, UIControl) assert width is None or isinstance(width, LayoutDimension) assert height is None or isinstance(height, LayoutDimension) assert get_width is None or callable(get_width) assert get_height is None or callable(get_height) assert width is None or get_width is None assert height is None or get_height is None 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) self.allow_scroll_beyond_bottom = to_cli_filter(allow_scroll_beyond_bottom) self.always_hide_cursor = to_cli_filter(always_hide_cursor) self.content = content self.dont_extend_width = dont_extend_width self.dont_extend_height = dont_extend_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._width = get_width or (lambda cli: width) self._height = get_height or (lambda cli: height) # Cache for the screens generated by the margin. self._margin_cache = SimpleLRUCache(maxsize=8) 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 #: Keep render information (mappings between buffer input and render #: output.) self.render_info = None def preferred_width(self, cli, max_available_width): # Width of the margins. total_margin_width = sum(m.get_width(cli) for m in self.left_margins + self.right_margins) # Window of the content. preferred_width = self.content.preferred_width( cli, max_available_width - total_margin_width) if preferred_width is not None: preferred_width += total_margin_width # Merge. return self._merge_dimensions( dimension=self._width(cli), preferred=preferred_width, dont_extend=self.dont_extend_width) def preferred_height(self, cli, width): return self._merge_dimensions( dimension=self._height(cli), preferred=self.content.preferred_height(cli, width), dont_extend=self.dont_extend_height) @staticmethod def _merge_dimensions(dimension, preferred=None, dont_extend=False): """ Take the LayoutDimension from this `Window` class and the received preferred size from the `UIControl` and return a `LayoutDimension` to report to the parent container. """ dimension = dimension or LayoutDimension() # When a preferred dimension was explicitly given to the Window, # ignore the UIControl. if dimension.preferred_specified: preferred = dimension.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: preferred = min(preferred, dimension.max) if dimension.min: 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 return LayoutDimension(min=dimension.min, max=max_, preferred=preferred) def write_to_screen(self, cli, screen, mouse_handlers, write_position): """ Write window to screen. This renders the user control, the margins and copies everything over to the absolute position at the given screen. """ # Calculate margin sizes. left_margin_widths = [m.get_width(cli) for m in self.left_margins] right_margin_widths = [m.get_width(cli) for m in self.right_margins] total_margin_width = sum(left_margin_widths + right_margin_widths) # Render UserControl. tpl = self.content.create_screen( cli, write_position.width - total_margin_width, write_position.height) if isinstance(tpl, tuple): temp_screen, highlighting = tpl else: # For backwards, compatibility. temp_screen, highlighting = tpl, defaultdict(lambda: defaultdict(lambda: None)) # Scroll content. applied_scroll_offsets = self._scroll( temp_screen, write_position.width - total_margin_width, write_position.height, cli) # Write body to screen. self._copy_body(cli, temp_screen, highlighting, screen, write_position, sum(left_margin_widths), write_position.width - total_margin_width, applied_scroll_offsets) # Remember render info. (Set before generating the margins. They need this.) self.render_info = WindowRenderInfo( original_screen=temp_screen, horizontal_scroll=self.horizontal_scroll, vertical_scroll=self.vertical_scroll, window_width=write_position.width, window_height=write_position.height, cursor_position=Point(y=temp_screen.cursor_position.y - self.vertical_scroll, x=temp_screen.cursor_position.x - self.horizontal_scroll), configured_scroll_offsets=self.scroll_offsets, applied_scroll_offsets=applied_scroll_offsets) # Set mouse handlers. def mouse_handler(cli, mouse_event): """ Wrapper around the mouse_handler of the `UIControl` that turns absolute coordinates into relative coordinates. """ position = mouse_event.position # Call the mouse handler of the UIControl first. result = self.content.mouse_handler( cli, MouseEvent( position=Point(x=position.x - write_position.xpos - sum(left_margin_widths), y=position.y - write_position.ypos + self.vertical_scroll), event_type=mouse_event.event_type)) # If it returns NotImplemented, handle it here. if result == NotImplemented: return self._mouse_handler(cli, 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 tokens. tokens = m.create_margin(cli, self.render_info, width, write_position.height) # Turn it into a screen. (Take a screen from the cache if we # already rendered those tokens using this size.) def create_screen(): return TokenListControl.static(tokens).create_screen( cli, width + 1, write_position.height) key = (tokens, width, write_position.height) return self._margin_cache.get(key, create_screen) for m, width in zip(self.left_margins, left_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 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 def _copy_body(self, cli, temp_screen, highlighting, new_screen, write_position, move_x, width, applied_scroll_offsets): """ Copy characters from the temp screen that we got from the `UIControl` to the real screen. """ xpos = write_position.xpos + move_x ypos = write_position.ypos height = write_position.height temp_buffer = temp_screen.data_buffer new_buffer = new_screen.data_buffer temp_screen_height = temp_screen.height vertical_scroll = self.vertical_scroll horizontal_scroll = self.horizontal_scroll y = 0 # Now copy the region we need to the real screen. for y in range(0, height): # We keep local row variables. (Don't look up the row in the dict # for each iteration of the nested loop.) new_row = new_buffer[y + ypos] if y >= temp_screen_height and y >= write_position.height: # Break out of for loop when we pass after the last row of the # temp screen. (We use the 'y' position for calculation of new # screen's height.) break else: temp_row = temp_buffer[y + vertical_scroll] highlighting_row = highlighting[y + vertical_scroll] # Copy row content, except for transparent tokens. # (This is useful in case of floats.) # Also apply highlighting. for x in range(0, width): cell = temp_row[x + horizontal_scroll] highlighting_token = highlighting_row[x] if highlighting_token: new_row[x + xpos] = Char(cell.char, highlighting_token) elif cell.token != Transparent: new_row[x + xpos] = cell if self.content.has_focus(cli): new_screen.cursor_position = Point(y=temp_screen.cursor_position.y + ypos - vertical_scroll, x=temp_screen.cursor_position.x + xpos - horizontal_scroll) if not self.always_hide_cursor(cli): new_screen.show_cursor = temp_screen.show_cursor if not new_screen.menu_position and temp_screen.menu_position: new_screen.menu_position = Point(y=temp_screen.menu_position.y + ypos - vertical_scroll, x=temp_screen.menu_position.x + xpos - horizontal_scroll) # Update height of the output screen. (new_screen.write_data is not # called, so the screen is not aware of its height.) new_screen.height = max(new_screen.height, ypos + y + 1) def _copy_margin(self, temp_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 temp_buffer = temp_screen.data_buffer new_buffer = new_screen.data_buffer # Now copy the region we need to the real screen. for y in range(0, write_position.height): new_row = new_buffer[y + ypos] temp_row = temp_buffer[y] # Copy row content, except for transparent tokens. # (This is useful in case of floats.) for x in range(0, width): cell = temp_row[x] if cell.token != Transparent: new_row[x + xpos] = cell def _scroll(self, temp_screen, width, height, cli): """ Scroll to make sure the cursor position is visible and that we maintain the requested scroll offset. Return the applied scroll offsets. """ 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(cli) 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 # Calculate the applied scroll offset. This value can be lower than what we had. scroll_offset_start = max(0, min(current_scroll, scroll_offset_start)) scroll_offset_end = max(0, min(content_size - current_scroll - window_size, scroll_offset_end)) return current_scroll, scroll_offset_start, scroll_offset_end # 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, scroll_offset_top, scroll_offset_bottom = do_scroll( current_scroll=self.vertical_scroll, scroll_offset_start=offsets.top, scroll_offset_end=offsets.bottom, cursor_pos=temp_screen.cursor_position.y, window_size=height, content_size=temp_screen.height) self.horizontal_scroll, scroll_offset_left, scroll_offset_right = do_scroll( current_scroll=self.horizontal_scroll, scroll_offset_start=offsets.left, scroll_offset_end=offsets.right, cursor_pos=temp_screen.cursor_position.x, window_size=width, content_size=temp_screen.width) applied_scroll_offsets = ScrollOffsets( top=scroll_offset_top, bottom=scroll_offset_bottom, left=scroll_offset_left, right=scroll_offset_right) return applied_scroll_offsets def _mouse_handler(self, cli, mouse_event): """ Mouse handler. Called when the UI control doesn't handle this particular event. """ if mouse_event.event_type == MouseEventTypes.SCROLL_DOWN: self._scroll_down(cli) elif mouse_event.event_type == MouseEventTypes.SCROLL_UP: self._scroll_up(cli) def _scroll_down(self, cli): " 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(cli) self.vertical_scroll += 1 def _scroll_up(self, cli): " Scroll window up. " info = self.render_info if info.vertical_scroll > 0: if info.cursor_position.y >= info.window_height - 1 - info.configured_scroll_offsets.bottom: self.content.move_cursor_up(cli) self.vertical_scroll -= 1 def walk(self, cli): # Only yield self. A window doesn't have children. yield self 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:`~prompt_toolkit.filters.CLIFilter` instance. """ def __init__(self, content, filter): assert isinstance(content, Container) self.content = content self.filter = to_cli_filter(filter) def reset(self): self.content.reset() def preferred_width(self, cli, max_available_width): if self.filter(cli): return self.content.preferred_width(cli, max_available_width) else: return LayoutDimension.exact(0) def preferred_height(self, cli, width): if self.filter(cli): return self.content.preferred_height(cli, width) else: return LayoutDimension.exact(0) def write_to_screen(self, cli, screen, mouse_handlers, write_position): if self.filter(cli): return self.content.write_to_screen(cli, screen, mouse_handlers, write_position) def walk(self, cli): return self.content.walk(cli) # Deprecated alias for 'Container'. Layout = Container prompt_toolkit-0.57/prompt_toolkit/layout/__init__.py0000644000175000017500000000362712606574247024722 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. container 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) |- 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) |- TokenListControl (Renders a simple list of tokens) |- FillControl (Fills control with one token/character.) `- 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 Float, FloatContainer, HSplit, VSplit, Window, ConditionalContainer from .controls import TokenListControl, FillControl, BufferControl prompt_toolkit-0.57/prompt_toolkit/enums.py0000644000175000017500000000125312623240275022754 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals class IncrementalSearchDirection(object): FORWARD = 'FORWARD' BACKWARD = 'BACKWARD' #: 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' # Dummy buffer. This is the buffer returned by # `CommandLineInterface.current_buffer` when the top of the `FocusStack` is # `None`. This could be the case when there is some widget has the focus and no # actual text editing is possible. This buffer should also never be displayed. # (It will never contain any actual text.) DUMMY_BUFFER = 'DUMMY_BUFFER' prompt_toolkit-0.57/prompt_toolkit/contrib/0000755000175000017500000000000012642647210022713 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/contrib/validators/0000755000175000017500000000000012642647210025063 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/contrib/validators/__init__.py0000644000175000000000000000000012556317626026337 0ustar jonathanroot00000000000000prompt_toolkit-0.57/prompt_toolkit/contrib/validators/base.py0000644000175000000000000000224712556317626025531 0ustar jonathanroot00000000000000from __future__ import unicode_literals from prompt_toolkit.validation import Validator, ValidationError from six import string_types class SentenceValidator(Validator): """ Validate input only when it appears in this list of sentences. :param sentences: List of sentences. :param ignore_case: If True, case-insensitive comparisons. """ def __init__(self, sentences, ignore_case=False, error_message='Invalid input', move_cursor_to_end=False): assert all(isinstance(s, string_types) for s in sentences) assert isinstance(ignore_case, bool) assert isinstance(error_message, string_types) self.sentences = list(sentences) self.ignore_case = ignore_case self.error_message = error_message self.move_cursor_to_end = move_cursor_to_end if ignore_case: self.sentences = {s.lower() for s in self.sentences} def validate(self, document): if document.text not in self.sentences: if self.move_cursor_to_end: index = len(document.text) else: index = 0 raise ValidationError(index=index, message=self.error_message) prompt_toolkit-0.57/prompt_toolkit/contrib/telnet/0000755000175000017500000000000012642647210024206 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/contrib/telnet/server.py0000644000175000017500000003111112642121273026057 0ustar jonathanjonathan00000000000000""" Telnet server. Example usage:: class MyTelnetApplication(TelnetApplication): def client_connected(self, telnet_connection): # Set CLI with simple prompt. telnet_connection.set_application( telnet_connection.create_prompt_application(...)) def handle_command(self, telnet_connection, document): # When the client enters a command, just reply. telnet_connection.send('You said: %r\n\n' % document.text) ... a = MyTelnetApplication() TelnetServer(application=a, host='127.0.0.1', port=23).run() """ from __future__ import unicode_literals import socket import select import threading import os import fcntl from six import int2byte, text_type, binary_type from codecs import getincrementaldecoder from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.eventloop.base import EventLoop from prompt_toolkit.interface import CommandLineInterface, Application from prompt_toolkit.layout.screen import Size from prompt_toolkit.shortcuts import create_prompt_application from prompt_toolkit.terminal.vt100_input import InputStream from prompt_toolkit.terminal.vt100_output import Vt100_Output from .log import logger from .protocol import IAC, DO, LINEMODE, SB, MODE, SE, WILL, ECHO, NAWS, SUPPRESS_GO_AHEAD from .protocol import TelnetProtocolParser from .application import TelnetApplication __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) 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.error("Couldn't send data over socket: %s" % e) self._buffer = [] class TelnetConnection(object): """ Class that represents one Telnet connection. """ def __init__(self, conn, addr, application, server, encoding): assert isinstance(addr, tuple) # (addr, port) tuple assert isinstance(application, TelnetApplication) assert isinstance(server, TelnetServer) assert isinstance(encoding, text_type) # e.g. 'utf-8' self.conn = conn self.addr = addr self.application = application self.closed = False self.handling_command = True self.server = server self.encoding = encoding self.callback = None # Function that handles the CLI result. # Create "Output" object. self.size = Size(rows=40, columns=79) # Initialize. _initialize_telnet(conn) # Create output. def get_size(): return self.size self.stdout = _ConnectionStdout(conn, encoding=encoding) self.vt100_output = Vt100_Output(self.stdout, get_size) # Create an eventloop (adaptor) for the CommandLineInterface. self.eventloop = _TelnetEventLoopInterface(server) # Set default CommandLineInterface. self.set_application(create_prompt_application()) # Call client_connected application.client_connected(self) # Draw for the first time. self.handling_command = False self.cli._redraw() def set_application(self, app, callback=None): """ Set ``CommandLineInterface`` instance for this connection. (This can be replaced any time.) :param cli: CommandLineInterface instance. :param callback: Callable that takes the result of the CLI. """ assert isinstance(app, Application) assert callback is None or callable(callback) self.cli = CommandLineInterface( application=app, eventloop=self.eventloop, output=self.vt100_output) self.callback = callback # Create a parser, and parser callbacks. cb = self.cli.create_eventloop_callbacks() inputstream = InputStream(cb.feed_key) # Input decoder for stdin. (Required when working with multibyte # characters, like chinese input.) stdin_decoder_cls = getincrementaldecoder(self.encoding) stdin_decoder = [stdin_decoder_cls()] # nonlocal # Tell the CLI that it's running. We don't start it through the run() # call, but will still want _redraw() to work. self.cli._is_running = True def data_received(data): """ TelnetProtocolParser 'data_received' callback """ assert isinstance(data, binary_type) try: result = stdin_decoder[0].decode(data) inputstream.feed(result) except UnicodeDecodeError: stdin_decoder[0] = stdin_decoder_cls() return '' def size_received(rows, columns): """ TelnetProtocolParser 'size_received' callback """ self.size = Size(rows=rows, columns=columns) cb.terminal_size_changed() self.parser = TelnetProtocolParser(data_received, size_received) def feed(self, data): """ Handler for incoming data. (Called by TelnetServer.) """ assert isinstance(data, binary_type) self.parser.feed(data) # Render again. self.cli._redraw() # When a return value has been set (enter was pressed), handle command. if self.cli.is_returning: try: return_value = self.cli.return_value() except (EOFError, KeyboardInterrupt) as e: # Control-D or Control-C was pressed. logger.info('%s, closing connection.', type(e).__name__) self.close() return # Handle CLI command self._handle_command(return_value) def _handle_command(self, command): """ Handle command. This will run in a separate thread, in order not to block the event loop. """ logger.info('Handle command %r', command) def in_executor(): self.handling_command = True try: if self.callback is not None: self.callback(self, command) finally: self.server.call_from_executor(done) def done(): self.handling_command = False # Reset state and draw again. (If the connection is still open -- # the application could have called TelnetConnection.close() if not self.closed: self.cli.reset() self.cli.buffers[DEFAULT_BUFFER].reset() self.cli.renderer.request_absolute_cursor_position() self.vt100_output.flush() self.cli._redraw() self.server.run_in_executor(in_executor) def erase_screen(self): """ Erase output screen. """ self.vt100_output.erase_screen() self.vt100_output.cursor_goto(0, 0) self.vt100_output.flush() def send(self, data): """ Send text to the client. """ assert isinstance(data, text_type) # When data is send back to the client, we should replace the line # endings. (We didn't allocate a real pseudo terminal, and the telnet # connection is raw, so we are responsible for inserting \r.) self.stdout.write(data.replace('\n', '\r\n')) self.stdout.flush() def close(self): """ Close the connection. """ self.application.client_leaving(self) self.conn.close() self.closed = True class _TelnetEventLoopInterface(EventLoop): """ Eventloop object to be assigned to `CommandLineInterface`. """ def __init__(self, server): self._server = server def close(self): " Ignore. " def stop(self): " Ignore. " def run_in_executor(self, callback): self._server.run_in_executor(callback) def call_from_executor(self, callback, _max_postpone_until=None): self._server.call_from_executor(callback) def add_reader(self, fd, callback): raise NotImplementedError def remove_reader(self, fd): raise NotImplementedError class TelnetServer(object): """ Telnet server implementation. """ def __init__(self, host='127.0.0.1', port=23, application=None, encoding='utf-8'): assert isinstance(host, text_type) assert isinstance(port, int) assert isinstance(application, TelnetApplication) assert isinstance(encoding, text_type) self.host = host self.port = port self.application = application self.encoding = encoding self.connections = set() self._calls_from_executor = [] # Create a pipe for inter thread communication. self._schedule_pipe = os.pipe() fcntl.fcntl(self._schedule_pipe[0], fcntl.F_SETFL, os.O_NONBLOCK) @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 run_in_executor(self, callback): threading.Thread(target=callback).start() def call_from_executor(self, callback): self._calls_from_executor.append(callback) if self._schedule_pipe: os.write(self._schedule_pipe[1], b'x') def _process_callbacks(self): """ Process callbacks from `call_from_executor` in eventloop. """ # Flush all the pipe content. os.read(self._schedule_pipe[0], 1024) # Process calls from executor. calls_from_executor, self._calls_from_executor = self._calls_from_executor, [] for c in calls_from_executor: c() def run(self): """ Run the eventloop for the telnet server. """ listen_socket = self.create_socket(self.host, self.port) logger.info('Listening for telnet connections on %s port %r', self.host, self.port) try: while True: # Removed closed connections. self.connections = set([c for c in self.connections if not c.closed]) # Ignore connections handling commands. connections = set([c for c in self.connections if not c.handling_command]) # Wait for next event. read_list = ( [listen_socket, self._schedule_pipe[0]] + [c.conn for c in connections]) read, _, _ = select.select(read_list, [], []) for s in read: # When the socket itself is ready, accept a new connection. if s == listen_socket: self._accept(listen_socket) # If we receive something on our "call_from_executor" pipe, process # these callbacks in a thread safe way. elif s == self._schedule_pipe[0]: self._process_callbacks() # Handle incoming data on socket. else: self._handle_incoming_data(s) finally: listen_socket.close() def _accept(self, listen_socket): """ Accept new incoming connection. """ conn, addr = listen_socket.accept() connection = TelnetConnection(conn, addr, self.application, self, encoding=self.encoding) self.connections.add(connection) logger.info('New connection %r %r', *addr) def _handle_incoming_data(self, conn): """ Handle incoming data on socket. """ connection = [c for c in self.connections if c.conn == conn][0] data = conn.recv(1024) if data: connection.feed(data) else: self.connections.remove(connection) prompt_toolkit-0.57/prompt_toolkit/contrib/telnet/protocol.py0000644000175000000000000001142412556317626025600 0ustar jonathanroot00000000000000""" 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 int2byte, binary_type, 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-0.57/prompt_toolkit/contrib/telnet/application.py0000644000175000000000000000157612556317626026251 0ustar jonathanroot00000000000000""" Interface for Telnet applications. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'TelnetApplication', ) class TelnetApplication(with_metaclass(ABCMeta, object)): """ The interface which has to be implemented for any telnet application. An instance of this class has to be passed to `TelnetServer`. """ @abstractmethod def client_connected(self, telnet_connection): """ Called when a new client was connected. Probably you want to call `telnet_connection.set_cli` here to set a the CommandLineInterface instance to be used. Hint: Use the following shortcut: `prompt_toolkit.shortcuts.create_cli` """ @abstractmethod def client_leaving(self, telnet_connection): """ Called when a client quits. """ prompt_toolkit-0.57/prompt_toolkit/contrib/telnet/__init__.py0000644000175000000000000000006112556317626025471 0ustar jonathanroot00000000000000from .server import * from .application import * prompt_toolkit-0.57/prompt_toolkit/contrib/telnet/log.py0000644000175000000000000000025212556317626024515 0ustar jonathanroot00000000000000""" Python logger for the telnet server. """ from __future__ import unicode_literals import logging logger = logging.getLogger(__package__) __all__ = ( 'logger', ) prompt_toolkit-0.57/prompt_toolkit/contrib/__init__.py0000644000175000000000000000000012556317626024167 0ustar jonathanroot00000000000000prompt_toolkit-0.57/prompt_toolkit/contrib/regular_languages/0000755000175000017500000000000012642647210026402 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/contrib/regular_languages/compiler.py0000644000175000017500000003636512620262265030602 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 .regex_parser import Any, Sequence, Regex, Variable, Repeat, Lookahead from .regex_parser import 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 redex 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 meight 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-0.57/prompt_toolkit/contrib/regular_languages/validation.py0000644000175000017500000000400112604630260031074 0ustar jonathanjonathan00000000000000""" Validator for a regular langage. """ from __future__ import unicode_literals from prompt_toolkit.validation import Validator, ValidationError from prompt_toolkit.document import Document 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( index=v.start + e.cursor_position, message=e.message) else: raise ValidationError(cursor_position=len(document.text), message='Invalid command') prompt_toolkit-0.57/prompt_toolkit/contrib/regular_languages/completion.py0000644000175000000000000000563712556317626030315 0ustar jonathanroot00000000000000""" 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-0.57/prompt_toolkit/contrib/regular_languages/__init__.py0000644000175000017500000000632612620262265030521 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-0.57/prompt_toolkit/contrib/regular_languages/regex_parser.py0000644000175000000000000001614212556317626030623 0ustar jonathanroot00000000000000""" 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 repitition 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 parantheses.") else: return result prompt_toolkit-0.57/prompt_toolkit/contrib/regular_languages/lexer.py0000644000175000017500000000507512622022711030071 0ustar jonathanjonathan00000000000000""" `GrammarLexer` is compatible with Pygments lexers and can be used to highlight the input using a regular grammar with token annotations. """ from __future__ import unicode_literals from pygments.token import Token from prompt_toolkit.layout.lexers import Lexer from .compiler import _CompiledGrammar __all__ = ( 'GrammarLexer', ) class GrammarLexer(Lexer): """ Lexer which can be used for highlighting of tokens 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 token, use a `prompt_toolkit.layout.lexers.SimpleLexer`. """ def __init__(self, compiled_grammar, default_token=None, lexers=None): assert isinstance(compiled_grammar, _CompiledGrammar) assert default_token is None or isinstance(default_token, tuple) 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_token = default_token or Token self.lexers = lexers or {} def get_tokens(self, cli, text): m = self.compiled_grammar.match_prefix(text) if m: characters = [[self.default_token, 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: lexer_tokens = lexer.get_tokens(cli, text[v.start:v.stop]) i = v.start for t, s in lexer_tokens: for c in s: if characters[i][0] == self.default_token: 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] = Token.TrailingInput return characters else: return [(Token, text)] prompt_toolkit-0.57/prompt_toolkit/contrib/completers/0000755000175000017500000000000012642647210025070 5ustar jonathanjonathan00000000000000prompt_toolkit-0.57/prompt_toolkit/contrib/completers/system.py0000644000175000017500000000357412642111147026772 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.contrib.regular_languages.completion import GrammarCompleter from prompt_toolkit.contrib.regular_languages.compiler import compile from .filesystem import PathCompleter, ExecutableCompleter __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 enterily 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-0.57/prompt_toolkit/contrib/completers/__init__.py0000644000175000000000000000022312556317626026353 0ustar jonathanroot00000000000000from __future__ import unicode_literals from .filesystem import PathCompleter from .base import WordCompleter from .system import SystemCompleter prompt_toolkit-0.57/prompt_toolkit/contrib/completers/filesystem.py0000644000175000017500000000723412622021546027630 0ustar jonathanjonathan00000000000000from __future__ import unicode_literals from prompt_toolkit.completion import Completer, Completion import os __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 themself.) filename += '/' else: if self.only_directories or not self.file_filter(full_name): continue yield Completion(completion, 0, display=filename) except OSError: pass class ExecutableCompleter(PathCompleter): """ Complete only excutable 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-0.57/prompt_toolkit/contrib/completers/base.py0000644000175000017500000000434012642072724026357 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. :param ignore_case: If True, case-insensitive completion. :param meta_dict: Optional dict mapping words to their meta-information. :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. """ def __init__(self, words, ignore_case=False, meta_dict=None, WORD=False, sentence=False, match_middle=False): assert not (WORD and sentence) self.words = list(words) self.ignore_case = ignore_case self.meta_dict = meta_dict or {} self.WORD = WORD self.sentence = sentence self.match_middle = match_middle assert all(isinstance(w, string_types) for w in self.words) def get_completions(self, document, complete_event): # 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) 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 self.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-0.57/prompt_toolkit/reactive.py0000644000175000017500000000346112623240275023432 0ustar jonathanjonathan00000000000000""" Prompt_toolkit is designed a way that the amount of changing state is reduced to a minimum. Where possible, code is written in a pure functional way. In general, this results in code where the flow is very easy to follow: the value of a variable can be deducted from its first assignment. However, often, practicality and performance beat purity and some classes still have a changing state. In order to not having to care too much about transferring states between several components we use some reactive programming. Actually some kind of data binding. We introduce two types: - Filter: for binding a boolean state. They can be chained using & and | operators. Have a look in the ``filters`` module. Resolving the actual value of a filter happens by calling it. - Integer: for binding integer values. Reactive operations (like addition and substraction) are not suppported. Resolving the actual value happens by casting it to int, like ``int(integer)``. This way, it is possible to use normal integers as well for static values. """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass class Integer(with_metaclass(ABCMeta, object)): """ Reactive integer -- anything that can be resolved to an ``int``. """ @abstractmethod def __int__(self): return 0 @classmethod def from_callable(cls, func): """ Create an Integer-like object that calls the given function when it is resolved to an int. """ return _IntegerFromCallable(func) Integer.register(int) class _IntegerFromCallable(Integer): def __init__(self, func=0): self.func = func def __repr__(self): return 'Integer.from_callable(%r)' % self.func def __int__(self): return int(self.func()) prompt_toolkit-0.57/prompt_toolkit/selection.py0000644000175000017500000000163612640634725023625 0ustar jonathanjonathan00000000000000""" Data structures for the selection. """ from __future__ import unicode_literals __all__ = ( 'SelectionType', '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 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-0.57/prompt_toolkit/buffer_mapping.py0000644000175000017500000000551212642110004024576 0ustar jonathanjonathan00000000000000""" The BufferMapping contains all the buffers for a command line interface, and it keeps track of which buffer gets the focus. """ from __future__ import unicode_literals from .enums import DEFAULT_BUFFER, SEARCH_BUFFER, SYSTEM_BUFFER, DUMMY_BUFFER from .buffer import Buffer, AcceptAction from .history import InMemoryHistory import six __all__ = ( 'BufferMapping', ) class BufferMapping(dict): """ Dictionary that maps the name of the buffers to the :class:`~prompt_toolkit.buffer.Buffer` instances. This mapping also keeps track of which buffer currently has the focus. (Some methods receive a 'cli' parameter. This is useful for applications where this `BufferMapping` is shared between several applications.) """ def __init__(self, buffers=None, initial=DEFAULT_BUFFER): assert buffers is None or isinstance(buffers, dict) # Start with an empty dict. super(BufferMapping, self).__init__() # Add default buffers. self.update({ # For the 'search' and 'system' buffers, 'returnable' is False, in # order to block normal Enter/ControlC behaviour. DEFAULT_BUFFER: Buffer(accept_action=AcceptAction.RETURN_DOCUMENT), SEARCH_BUFFER: Buffer(history=InMemoryHistory(), accept_action=AcceptAction.IGNORE), SYSTEM_BUFFER: Buffer(history=InMemoryHistory(), accept_action=AcceptAction.IGNORE), DUMMY_BUFFER: Buffer(read_only=True), }) # Add received buffers. if buffers is not None: self.update(buffers) # Focus stack. self.focus_stack = [initial or DEFAULT_BUFFER] def current(self, cli): """ The active :class:`.Buffer`. """ return self[self.focus_stack[-1]] def current_name(self, cli): """ The name of the active :class:`.Buffer`. """ return self.focus_stack[-1] def previous(self, cli): """ Return the previously focussed :class:`.Buffer` or `None`. """ if len(self.focus_stack) > 1: try: return self[self.focus_stack[-2]] except KeyError: pass def focus(self, cli, buffer_name): """ Focus the buffer with the given name. """ assert isinstance(buffer_name, six.text_type) self.focus_stack = [buffer_name] def push_focus(self, cli, buffer_name): """ Push buffer on the focus stack. """ assert isinstance(buffer_name, six.text_type) self.focus_stack.append(buffer_name) def pop_focus(self, cli): """ Pop buffer from the focus stack. """ if len(self.focus_stack) > 1: self.focus_stack.pop() else: raise IndexError('Cannot pop last item from the focus stack.') prompt_toolkit-0.57/prompt_toolkit/buffer.py0000644000175000017500000012100512640634725023102 0ustar jonathanjonathan00000000000000""" Data structures for the Buffer. It holds the text, cursor position, history, etc... """ from __future__ import unicode_literals from .auto_suggest import AutoSuggest from .clipboard import ClipboardData from .completion import Completer, Completion, CompleteEvent from .document import Document from .enums import IncrementalSearchDirection from .filters import to_simple_filter from .history import History, InMemoryHistory from .search_state import SearchState from .selection import SelectionType, SelectionState from .utils import Callback from .validation import ValidationError import os import six import subprocess import tempfile __all__ = ( 'EditReadOnlyBuffer', 'AcceptAction', 'Buffer', 'indent', 'unindent', ) class EditReadOnlyBuffer(Exception): " Attempt editing of read-only :class:`.Buffer`. " class AcceptAction(object): """ What to do when the input is accepted by the user. (When Enter was pressed in the command line.) :param handler: (optional) A callable which takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and :class:`~prompt_toolkit.document.Document`. It is called when the user accepts input. :param render_cli_done: When using a handler, first render the CLI in the 'done' state, then call the handler. This """ def __init__(self, handler=None): assert handler is None or callable(handler) self.handler = handler @classmethod def run_in_terminal(cls, handler, render_cli_done=False): """ Create an :class:`.AcceptAction` that runs the given handler in the terminal. :param render_cli_done: When True, render the interface in the 'Done' state first, then execute the function. If False, erase the interface instead. """ def _handler(cli, buffer): cli.run_in_terminal(lambda: handler(cli, buffer), render_cli_done=render_cli_done) return AcceptAction(handler=_handler) @property def is_returnable(self): """ True when there is something handling accept. """ return bool(self.handler) def validate_and_handle(self, cli, buffer): """ Validate buffer and handle the accept action. """ if buffer.validate(): if self.handler: self.handler(cli, buffer) buffer.append_to_history() def _return_document_handler(cli, buffer): cli.set_return_value(buffer.document) AcceptAction.RETURN_DOCUMENT = AcceptAction(_return_document_handler) AcceptAction.IGNORE = AcceptAction(handler=None) class CompletionState(object): """ Immutable class that contains a completion state. """ def __init__(self, original_document, current_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.current_completions = current_completions or [] #: Position in the `current_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.current_completions), self.complete_index) def go_to_index(self, index): """ Create a new :class:`.CompletionState` object with the new index. """ return CompletionState(self.original_document, self.current_completions, 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.current_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.current_completions[self.complete_index] class Buffer(object): """ The core data structure that holds the text and cursor position of the current input line and implements all text manupulations 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: Suffix to be appended to the tempfile for the 'open in editor' function. Events: :param on_text_changed: Callback instance or None. :param on_text_insert: Callback instance or None. :param on_cursor_position_changed: Callback instance or None. Filters: :param is_multiline: :class:`~prompt_toolkit.filters.SimpleFilter` to indicate whether we should consider this buffer a multiline input. If so, key bindings can decide to insert newlines when pressing [Enter]. (Instead of accepting the input.) :param complete_while_typing: :class:`~prompt_toolkit.filters.SimpleFilter` instance. Decide whether or not to do asynchronous autocompleting while typing. :param enable_history_search: :class:`~prompt_toolkit.filters.SimpleFilter` to indicate when up-arrow partial string matching is enabled. It is adviced 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.SimpleFilter`. When True, changes will not be allowed. """ def __init__(self, completer=None, auto_suggest=None, history=None, validator=None, tempfile_suffix='', is_multiline=False, complete_while_typing=False, enable_history_search=False, initial_document=None, accept_action=AcceptAction.IGNORE, read_only=False, on_text_changed=None, on_text_insert=None, on_cursor_position_changed=None): # Accept both filters and booleans as input. enable_history_search = to_simple_filter(enable_history_search) is_multiline = to_simple_filter(is_multiline) complete_while_typing = to_simple_filter(complete_while_typing) read_only = to_simple_filter(read_only) # 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 on_text_changed is None or isinstance(on_text_changed, Callback) assert on_text_insert is None or isinstance(on_text_insert, Callback) assert on_cursor_position_changed is None or isinstance(on_cursor_position_changed, Callback) self.completer = completer self.auto_suggest = auto_suggest self.validator = validator self.tempfile_suffix = tempfile_suffix self.accept_action = accept_action # Filters. (Usually, used by the key bindings to drive the buffer.) self.is_multiline = is_multiline self.complete_while_typing = complete_while_typing self.enable_history_search = enable_history_search self.read_only = read_only #: 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 = on_text_changed or Callback() self.on_text_insert = on_text_insert or Callback() self.on_cursor_position_changed = on_cursor_position_changed or Callback() self.reset(initial_document=initial_document) def reset(self, initial_document=None, append_to_history=False): """ :param append_to_history: Append current input to history first. """ assert initial_document is None or isinstance(initial_document, Document) if append_to_history: self.append_to_history() initial_document = initial_document or Document() self.__cursor_position = initial_document.cursor_position # `ValidationError` instance. (Will be set when the input is wrong.) self.validation_error = None # State of the selection. self.selection_state = None # State of complete browser self.complete_state = None # For interactive completion through Ctrl-N/Ctrl-P. # 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.strings[:] self._working_lines.append(initial_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. """ original_value = self._working_lines[self.working_index] self._working_lines[self.working_index] = value return value != original_value 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 assert 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. 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) assert value <= len(self.text) 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 self._text_changed() def _text_changed(self): # Remove any validation errors and complete state. self.validation_error = None self.complete_state = None self.selection_state = None self.suggestion = None # fire 'on_text_changed' event. self.on_text_changed.fire() def _cursor_position_changed(self): # Remove any validation errors and complete state. self.validation_error = None self.complete_state = 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 and cursor position. """ return Document(self.text, self.cursor_position, selection=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. """ 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() if cursor_position_changed: self._cursor_position_changed() # 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_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. """ self.cursor_position += self.document.get_cursor_up_position(count=count) def cursor_down(self, count=1): """ (for multiline edit). Move cursor to the next line. """ self.cursor_position += self.document.get_cursor_down_position(count=count) def auto_up(self, count=1): """ 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_position += self.document.get_cursor_up_position(count=count) elif not self.selection_state: self.history_backward(count=count) def auto_down(self, count=1): """ 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_position += self.document.get_cursor_down_position(count=count) elif not self.selection_state: self.history_forward(count=count) def delete_before_cursor(self, count=1): """ Delete character before cursor, return deleted character. """ 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 one character. Return deleted character. """ 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): """ 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 + ' ' + self.document.text_after_cursor.lstrip(' ')) def join_selected_lines(self): """ 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(' ') + ' ' 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.current_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.current_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, go_to_first=True, go_to_last=False): """ Start completions. (Generate list of completions and initialize.) """ assert not (go_to_first and go_to_last) # Generate list of all completions. if completions is None: if self.completer: completions = list(self.completer.get_completions( self.document, CompleteEvent(completion_requested=True) )) else: completions = [] # Set `complete_state`. if completions: self.complete_state = CompletionState( original_document=self.document, current_completions=completions) if go_to_first: self.go_to_completion(0) elif go_to_last: self.go_to_completion(len(completions) - 1) else: self.go_to_completion(None) else: self.complete_state = None 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]) 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.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`. """ if self.enable_history_search(): if self.history_search_text is None: self.history_search_text = self.text 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. :param history_search: When True, filter history using ``self.history_search_text``. """ 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 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. """ 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, before=False, count=1): """ Insert the data from the clipboard. """ assert isinstance(data, ClipboardData) self.document = self.document.paste_clipboard_data(data, before=before, count=count) 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. """ # 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 = self.text[self.cursor_position:self.cursor_position+len(data)] if '\n' in overwritten_text: overwritten_text = overwritten_text[:overwritten_text.find('\n')] self.text = self.text[:self.cursor_position] + data + self.text[self.cursor_position+len(overwritten_text):] else: self.text = self.text[:self.cursor_position] + data + self.text[self.cursor_position:] if move_cursor: self.cursor_position += len(data) # Fire 'on_text_insert' event. if fire_event: self.on_text_insert.fire() 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): """ Returns `True` if valid. """ self.validation_error = None # Validate first. If not valid, set validation exception. if self.validator: try: self.validator.validate(self.document) except ValidationError as e: # Set cursor position (don't allow invalid values.) cursor_position = e.cursor_position self.cursor_position = min(max(0, cursor_position), len(self.text)) self.validation_error = e return False return True def append_to_history(self): """ Append the current input to the history. (Only if valid input.) """ # Validate first. If not valid, set validation exception. if not self.validate(): return # Save at the tail of the history. (But don't if the last entry the # history is already the same.) if self.text and (not len(self.history) or self.history[-1] != self.text): self.history.append(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 == IncrementalSearchDirection.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. """ 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 return Document(self._working_lines[working_index], 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, cli): """ Open code in editor. :param cli: :class:`~prompt_toolkit.interface.CommandLineInterface` instance. """ if self.read_only(): raise EditReadOnlyBuffer() # Write to temporary file descriptor, filename = tempfile.mkstemp(self.tempfile_suffix) os.write(descriptor, self.text.encode('utf-8')) os.close(descriptor) # Open in editor # (We need to use `cli.run_in_terminal`, because not all editors go to # the alternate screen buffer, and some could influence the cursor # position.) succes = cli.run_in_terminal(lambda: self._open_file_in_editor(filename)) # 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)) # Clean up temp file. os.remove(filename) def _open_file_in_editor(self, filename): """ Call editor executable. Return True when we received a zero return code. """ # If the 'EDITOR' environment variable has been set, use that one. # Otherwise, fall back to the first available editor that we can find. editor = os.environ.get('EDITOR') editors = [ 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: returncode = subprocess.call([e, filename]) return returncode == 0 except OSError: # Executable does not exist, try the next one. pass return False 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) prompt_toolkit-0.57/prompt_toolkit/completion.py0000644000175000017500000001114412623240275023776 0ustar jonathanjonathan00000000000000""" """ from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from six import with_metaclass __all__ = ( 'Completion', 'Completer', 'CompleteEvent', '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) If the completion has to be displayed differently in the completion menu. :param display_meta: (Optional string) Meta information about the completion, e.g. the path or source where it's coming from. :param get_display_meta: Lazy `display_meta`. Retrieve meta information only when meta is displayed. """ def __init__(self, text, start_position=0, display=None, display_meta=None, get_display_meta=None): self.text = text self.start_position = start_position self._display_meta = display_meta self._get_display_meta = get_display_meta if display is None: self.display = text else: self.display = display assert self.start_position <= 0 def __repr__(self): return '%s(text=%r, start_position=%r)' % ( self.__class__.__name__, self.text, self.start_position) 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_meta(self): # Return meta-text. (This is lazy when using "get_display_meta".) if self._display_meta is not None: return self._display_meta elif self._get_display_meta: self._display_meta = self._get_display_meta() return self._display_meta else: return '' 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 explicitely pressed the `Tab` key in order to view the completions. These two flags can be used for instance to implemented 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 explicitely 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): """ Yield :class:`.Completion` instances. :param document: :class:`~prompt_toolkit.document.Document` instance. :param complete_event: :class:`.CompleteEvent` instance. """ while False: yield 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) completions = [c for c in completions if doesnt_change_before_cursor(c)] # Return the common prefix. def get_suffix(completion): return completion.text[-completion.start_position:] return _commonprefix([get_suffix(c) for c in completions]) 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-0.57/prompt_toolkit/shortcuts.py0000644000175000017500000005151512642052176023673 0ustar jonathanjonathan00000000000000""" Shortcuts for retrieving input from the user. If you are using this library for retrieving some input from the user (as a pure Python replacement for GNU readline), probably for 90% of the use cases, the :func:`.prompt` function is all you need. It's the easiest shortcut which does a lot of the underlying work like creating a :class:`~prompt_toolkit.interface.CommandLineInterface` instance for you. When is this not sufficient: - When you want to have more complicated layouts (maybe with sidebars or multiple toolbars. Or visibility of certain user interface controls according to some conditions.) - When you wish to have multiple input buffers. (If you would create an editor like a Vi clone.) - Something else that requires more customization than what is possible with the parameters of `prompt`. In that case, study the code in this file and build your own `CommandLineInterface` instance. It's not too complicated. """ from __future__ import unicode_literals from .buffer import Buffer, AcceptAction from .document import Document from .enums import DEFAULT_BUFFER, SEARCH_BUFFER from .filters import IsDone, HasFocus, RendererHeightIsKnown, to_simple_filter, to_cli_filter, Condition from .history import InMemoryHistory from .interface import CommandLineInterface, Application, AbortAction from .key_binding.manager import KeyBindingManager from .layout import Window, HSplit, VSplit, FloatContainer, Float from .layout.containers import ConditionalContainer from .layout.controls import BufferControl, TokenListControl from .layout.dimension import LayoutDimension from .layout.lexers import PygmentsLexer from .layout.menus import CompletionsMenu, MultiColumnCompletionsMenu from .layout.processors import PasswordProcessor, ConditionalProcessor, AppendAutoSuggestion from .layout.highlighters import SearchHighlighter, SelectionHighlighter, ConditionalHighlighter from .layout.prompt import DefaultPrompt from .layout.screen import Char from .layout.toolbars import ValidationToolbar, SystemToolbar, ArgToolbar, SearchToolbar from .layout.utils import explode_tokens from .styles import DEFAULT_STYLE, PygmentsStyle from .utils import is_conemu_ansi, is_windows, DummyContext from pygments.token import Token from six import text_type, exec_ import pygments.lexer import sys import textwrap if is_windows(): from .terminal.win32_output import Win32Output from .terminal.conemu_output import ConEmuOutput else: from .terminal.vt100_output import Vt100_Output __all__ = ( 'create_eventloop', 'create_output', 'create_prompt_layout', 'create_prompt_application', 'prompt', 'prompt_async', ) def create_eventloop(inputhook=None): """ Create and return an :class:`~prompt_toolkit.eventloop.base.EventLoop` instance for a :class:`~prompt_toolkit.interface.CommandLineInterface`. """ if is_windows(): from prompt_toolkit.eventloop.win32 import Win32EventLoop as Loop else: from prompt_toolkit.eventloop.posix import PosixEventLoop as Loop return Loop(inputhook=inputhook) def create_output(stdout=None, true_color=False): """ Return an :class:`~prompt_toolkit.output.Output` instance for the command line. :param true_color: When True, use 24bit colors instead of 256 colors. (`bool` or :class:`~prompt_toolkit.filters.SimpleFilter`.) """ stdout = stdout or sys.__stdout__ true_color = to_simple_filter(true_color) if is_windows(): if is_conemu_ansi(): return ConEmuOutput(stdout) else: return Win32Output(stdout) else: return Vt100_Output.from_pty(stdout, true_color=true_color) def create_asyncio_eventloop(loop=None): """ Returns an asyncio :class:`~prompt_toolkit.eventloop.EventLoop` instance for usage in a :class:`~prompt_toolkit.interface.CommandLineInterface`. It is a wrapper around an asyncio loop. :param loop: The asyncio eventloop (or `None` if the default asyncioloop should be used.) """ # 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(loop) def _split_multiline_prompt(get_prompt_tokens): """ Take a `get_prompt_tokens` function. and return two new functions instead. One that returns the tokens to be shown on the lines above the input, and another one with the tokens to be shown at the first line of the input. """ def before(cli): result = [] found_nl = False for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): if char == '\n': found_nl = True elif found_nl: result.insert(0, (token, char)) return result def first_input_line(cli): result = [] for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): if char == '\n': break else: result.insert(0, (token, char)) return result return before, first_input_line def create_prompt_layout(message='', lexer=None, is_password=False, reserve_space_for_menu=8, get_prompt_tokens=None, get_bottom_toolbar_tokens=None, display_completions_in_columns=False, extra_input_processors=None, multiline=False, wrap_lines=True): """ Create a :class:`.Container` instance for a prompt. :param message: Text to be used as prompt. :param lexer: :class:`~prompt_toolkit.layout.lexers.Lexer` to be used for the highlighting. :param is_password: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. When True, display input as '*'. :param reserve_space_for_menu: Space to be reserved for the menu. When >0, make sure that a minimal height is allocated in the terminal, in order to display the completion menu. :param get_prompt_tokens: An optional callable that returns the tokens to be shown in the menu. (To be used instead of a `message`.) :param get_bottom_toolbar_tokens: An optional callable that returns the tokens for a toolbar at the bottom. :param display_completions_in_columns: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in multiple columns. :param multiline: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. 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.CLIFilter`. When True (the default), automatically wrap long lines instead of scrolling horizontally. """ assert isinstance(message, text_type) assert get_bottom_toolbar_tokens is None or callable(get_bottom_toolbar_tokens) assert get_prompt_tokens is None or callable(get_prompt_tokens) assert not (message and get_prompt_tokens) display_completions_in_columns = to_cli_filter(display_completions_in_columns) multiline = to_cli_filter(multiline) if get_prompt_tokens is None: get_prompt_tokens = lambda _: [(Token.Prompt, message)] get_prompt_tokens_1, get_prompt_tokens_2 = _split_multiline_prompt(get_prompt_tokens) # `lexer` is supposed to be a `Lexer` instance. But if a Pygments lexer # class is given, turn it into a PygmentsLexer. (Important for # backwards-compatibility.) try: if issubclass(lexer, pygments.lexer.Lexer): lexer = PygmentsLexer(lexer) except TypeError: # Happens when lexer is `None` or an instance of something else. pass # Create highlighters and processors list. highlighters = [ ConditionalHighlighter( # By default, only highlight search when the search # input has the focus. (Note that this doesn't mean # there is no search: the Vi 'n' binding for instance # still allows to jump to the next match in # navigation mode.) SearchHighlighter(preview_search=True), HasFocus(SEARCH_BUFFER)), SelectionHighlighter()] input_processors = [ ConditionalProcessor(AppendAutoSuggestion(), HasFocus(DEFAULT_BUFFER) & ~IsDone()), ConditionalProcessor(PasswordProcessor(), is_password)] if extra_input_processors: input_processors.extend(extra_input_processors) # Show the prompt before the input (using the DefaultPrompt processor. # This also replaces it with reverse-i-search and 'arg' when required. # (Only for single line mode.) # (DefaultPrompt should always be at the end of the processors.) input_processors.append(ConditionalProcessor( DefaultPrompt(get_prompt_tokens), ~multiline)) # Create bottom toolbar. if get_bottom_toolbar_tokens: toolbars = [ConditionalContainer( Window(TokenListControl(get_bottom_toolbar_tokens, default_char=Char(' ', Token.Toolbar)), height=LayoutDimension.exact(1)), filter=~IsDone() & RendererHeightIsKnown())] else: toolbars = [] def get_height(cli): # 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 reserve_space_for_menu and not cli.is_done: return LayoutDimension(min=reserve_space_for_menu) else: return LayoutDimension() # Create and return Container instance. return HSplit([ ConditionalContainer( Window( TokenListControl(get_prompt_tokens_1), dont_extend_height=True), filter=multiline, ), VSplit([ # In multiline mode, the prompt is displayed in a left pane. ConditionalContainer( Window( TokenListControl(get_prompt_tokens_2), dont_extend_width=True, ), filter=multiline, ), # The main input, with completion menus floating on top of it. FloatContainer( Window( BufferControl( highlighters=highlighters, input_processors=input_processors, lexer=lexer, wrap_lines=wrap_lines, # Enable preview_search, we want to have immediate feedback # in reverse-i-search mode. preview_search=True), get_height=get_height, ), [ Float(xcursor=True, ycursor=True, content=CompletionsMenu( max_height=16, scroll_offset=1, extra_filter=HasFocus(DEFAULT_BUFFER) & ~display_completions_in_columns)), Float(xcursor=True, ycursor=True, content=MultiColumnCompletionsMenu( extra_filter=HasFocus(DEFAULT_BUFFER) & display_completions_in_columns, show_meta=True)) ] ), ]), ValidationToolbar(), SystemToolbar(), # In multiline mode, we use two toolbars for 'arg' and 'search'. ConditionalContainer(ArgToolbar(), multiline), ConditionalContainer(SearchToolbar(), multiline), ] + toolbars) def create_prompt_application( message='', multiline=False, wrap_lines=True, is_password=False, vi_mode=False, complete_while_typing=True, enable_history_search=False, lexer=None, enable_system_bindings=False, enable_open_in_editor=False, validator=None, completer=None, reserve_space_for_menu=8, auto_suggest=None, style=None, history=None, clipboard=None, get_prompt_tokens=None, get_bottom_toolbar_tokens=None, display_completions_in_columns=False, get_title=None, mouse_support=False, extra_input_processors=None, key_bindings_registry=None, on_abort=AbortAction.RAISE_EXCEPTION, on_exit=AbortAction.RAISE_EXCEPTION, accept_action=AcceptAction.RETURN_DOCUMENT, default=''): """ Create an :class:`~Application` instance for a prompt. (It is meant to cover 90% of the prompt use cases, where no extreme customization is required. For more complex input, it is required to create a custom :class:`~Application` instance.) :param message: Text to be shown before the prompt. :param mulitiline: Allow multiline input. Pressing enter will insert a newline. (This requires Meta+Enter to accept the input.) :param wrap_lines: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. When True (the default), automatically wrap long lines instead of scrolling horizontally. :param is_password: Show asterisks instead of the actual typed characters. :param vi_mode: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. If True, use Vi key bindings. :param complete_while_typing: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Enable autocompletion while typing. :param enable_history_search: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Enable up-arrow parting string matching. :param lexer: :class:`~prompt_toolkit.layout.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 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: Pygments style class for the color scheme. :param enable_system_bindings: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Pressing Meta+'!' will show a system prompt. :param enable_open_in_editor: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. 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.base.Clipboard` instance. (e.g. :class:`~prompt_toolkit.clipboard.in_memory.InMemoryClipboard`) :param get_bottom_toolbar_tokens: Optional callable which takes a :class:`~prompt_toolkit.interface.CommandLineInterface` and returns a list of tokens for the bottom toolbar. :param display_completions_in_columns: `bool` or :class:`~prompt_toolkit.filters.CLIFilter`. Display the completions in multiple columns. :param get_title: Callable that returns the title to be displayed in the terminal. :param mouse_support: `bool` or :class:`~prompt_toolkit.filters.CLIFilter` to enable mouse support. :param default: The default text to be shown in the input buffer. (This can be edited by the user.) """ if key_bindings_registry is None: key_bindings_registry = KeyBindingManager.for_prompt( enable_vi_mode=vi_mode, enable_system_bindings=enable_system_bindings, enable_open_in_editor=enable_open_in_editor).registry # Make sure that complete_while_typing is disabled when enable_history_search # is enabled. (First convert to SimpleFilter, to avoid doing bitwise operations # on bool objects.) complete_while_typing = to_simple_filter(complete_while_typing) enable_history_search = to_simple_filter(enable_history_search) multiline = to_simple_filter(multiline) complete_while_typing = complete_while_typing & ~enable_history_search # Accept Pygments styles as well for backwards compatibility. try: if issubclass(style, pygments.style.Style): style = PygmentsStyle(style) except TypeError: # Happens when style is `None` or an instance of something else. pass # Create application return Application( layout=create_prompt_layout( message=message, lexer=lexer, is_password=is_password, reserve_space_for_menu=(reserve_space_for_menu if completer is not None else 0), multiline=Condition(lambda cli: multiline()), get_prompt_tokens=get_prompt_tokens, get_bottom_toolbar_tokens=get_bottom_toolbar_tokens, display_completions_in_columns=display_completions_in_columns, extra_input_processors=extra_input_processors, wrap_lines=wrap_lines), buffer=Buffer( enable_history_search=enable_history_search, complete_while_typing=complete_while_typing, is_multiline=multiline, history=(history or InMemoryHistory()), validator=validator, completer=completer, auto_suggest=auto_suggest, accept_action=accept_action, initial_document=Document(default), ), style=style or DEFAULT_STYLE, clipboard=clipboard, key_bindings_registry=key_bindings_registry, get_title=get_title, mouse_support=mouse_support, on_abort=on_abort, on_exit=on_exit) def prompt(message='', **kwargs): """ Get input from the user and return it. This is a wrapper around a lot of ``prompt_toolkit`` functionality and can be a replacement for `raw_input`. (or GNU readline.) If you want to keep your history across several calls, create one :class:`~prompt_toolkit.history.History` instance and pass it every time. This function accepts many keyword arguments. Except for the following, they are a proxy to the arguments of :func:`.create_prompt_application`. :param patch_stdout: Replace ``sys.stdout`` by a proxy that ensures that print statements from other threads won't destroy the prompt. (They will be printed above the prompt instead.) :param return_asyncio_coroutine: When True, return a asyncio coroutine. (Python >3.3) :param true_color: When True, use 24bit colors instead of 256 colors. """ patch_stdout = kwargs.pop('patch_stdout', False) return_asyncio_coroutine = kwargs.pop('return_asyncio_coroutine', False) true_color = kwargs.pop('true_color', False) if return_asyncio_coroutine: eventloop = create_asyncio_eventloop() else: eventloop = kwargs.pop('eventloop', None) or create_eventloop() # Create CommandLineInterface. cli = CommandLineInterface( application=create_prompt_application(message, **kwargs), eventloop=eventloop, output=create_output(true_color=true_color)) # Replace stdout. patch_context = cli.patch_stdout_context() if patch_stdout else DummyContext() # Read input and return it. if return_asyncio_coroutine: # Create an asyncio coroutine and call it. exec_context = {'patch_context': patch_context, 'cli': cli} exec_(textwrap.dedent(''' import asyncio @asyncio.coroutine def prompt_coro(): with patch_context: document = yield from cli.run_async(reset_current_buffer=False) if document: return document.text '''), exec_context) return exec_context['prompt_coro']() else: # Note: We pass `reset_current_buffer=False`, because that way it's easy to # give DEFAULT_BUFFER a default value, without it getting erased. We # don't have to reset anyway, because this is the first and only time # that this CommandLineInterface will run. try: with patch_context: document = cli.run(reset_current_buffer=False) if document: return document.text finally: eventloop.close() def prompt_async(message='', **kwargs): """ Similar to :func:`.prompt`, but return an asyncio coroutine instead. """ kwargs['return_asyncio_coroutine'] = True return prompt(message, **kwargs) # Deprecated alias for `prompt`. get_input = prompt prompt_toolkit-0.57/prompt_toolkit/__init__.py0000644000175000017500000000107412642646266023400 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 meight also want to have a look at `prompt_toolkit.shortcuts.prompt`. """ from .interface import CommandLineInterface from .application import AbortAction, Application from .shortcuts import prompt __version__ = '0.57' prompt_toolkit-0.57/prompt_toolkit/win32_types.py0000644000175000017500000000771612623240275024025 0ustar jonathanjonathan00000000000000from ctypes import Union, Structure, c_char, c_short, c_long, c_ulong from ctypes.wintypes import DWORD, BOOL, LPVOID, WORD, WCHAR # 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-0.57/README.rst0000666000175000017500000001347712642305710017670 0ustar jonathanjonathan00000000000000Python Prompt Toolkit ===================== |Build Status| |PyPI| ``prompt_toolkit`` is a library for building powerful interactive command lines and terminal applications in Python. Read the `documentation on readthedocs `_. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt-toolkit. .. image :: https://github.com/jonathanslenders/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.) - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.4. - 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. - Code written with love. - Runs on Linux, OS X, OpenBSD and Windows systems. 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, you need to add ``from __future__ import unicode_literals`` to the above example. All strings are expected to be unicode strings. Projects using prompt-toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql 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. Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure 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 focussing 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 initialisation). 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/jonathanslenders/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit# .. |PyPI| image:: https://pypip.in/version/prompt-toolkit/badge.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version prompt_toolkit-0.57/TODO.rst0000644000175000017500000000276412574430226017476 0ustar jonathanjonathan00000000000000 - Focus stack needs to accept values other than `None`. This way we can better give the sidebar and exit message focus in ptpython, and it doesn't break focus in unpredictable ways in ptpdb. - Margins in BufferControl needs to be rendered independently. That way we can clean up code in the Screen class (which should not be responsible for calling margins.) The Screen class should however keep a mapping of the line numbers to input lines. This will also make it possible to invalidate margins independently of the main content. (Probably it's not worth having invalidation on the margins -- it's not heavy to calculate.) - Find a way to support backgrounds. A.k.a: find a way to eleminate Pygments. - support editing of larger buffers. Eleminate pygments ------------------ We need two things: - Tokens to identify chunks of text. - A stylesheet. For both, we still want to be able to use Pygments, by wrapping them, without loosing any performance. Pygments limitations: - Tokens have just one class, unlike CSS. A in HTML can have several classes, where each class contributes to a part of the styling. (E.g. the background.) Token.History.Something Editing of larger buffers ------------------------- We need two things: - A way to incrementally edit text of large buffers. (Keeping history.) - A way to incrementally lex the result of such a large buffer. Renames: ------- WindowRenderInfo.rendered_height -> window_height WindowRenderInfo.input_line_to_screen_line -> should become property. prompt_toolkit-0.57/PKG-INFO0000644000175000017500000001655512642647210017276 0ustar jonathanjonathan00000000000000Metadata-Version: 1.0 Name: prompt_toolkit Version: 0.57 Summary: Library for building powerful interactive command lines in Python Home-page: https://github.com/jonathanslenders/python-prompt-toolkit Author: Jonathan Slenders Author-email: UNKNOWN License: LICENSE.txt Description: Python Prompt Toolkit ===================== |Build Status| |PyPI| ``prompt_toolkit`` is a library for building powerful interactive command lines and terminal applications in Python. Read the `documentation on readthedocs `_. Ptpython ******** `ptpython `_ is an interactive Python Shell, build on top of prompt-toolkit. .. image :: https://github.com/jonathanslenders/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.) - Reverse and forward incremental search. - Runs on all Python versions from 2.6 up to 3.4. - 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. - Code written with love. - Runs on Linux, OS X, OpenBSD and Windows systems. 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, you need to add ``from __future__ import unicode_literals`` to the above example. All strings are expected to be unicode strings. Projects using prompt-toolkit ***************************** Shells: - `ptpython `_: Python REPL - `ptpdb `_: Python debugger (pdb replacement) - `pgcli `_: Postgres client. - `mycli `_: MySql 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. Full screen applications: - `pymux `_: A terminal multiplexer (like tmux) in pure Python. - `pyvim `_: A Vim clone in pure 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 focussing 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 initialisation). 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/jonathanslenders/python-prompt-toolkit.svg?branch=master :target: https://travis-ci.org/jonathanslenders/python-prompt-toolkit# .. |PyPI| image:: https://pypip.in/version/prompt-toolkit/badge.svg :target: https://pypi.python.org/pypi/prompt-toolkit/ :alt: Latest Version Platform: UNKNOWN