diff options
author | Ivan Blinkov <ivan@blinkov.ru> | 2022-02-10 16:47:10 +0300 |
---|---|---|
committer | Daniil Cherednik <dcherednik@yandex-team.ru> | 2022-02-10 16:47:10 +0300 |
commit | 1aeb9a455974457866f78722ad98114bafc84e8a (patch) | |
tree | e4340eaf1668684d83a0a58c36947c5def5350ad /contrib/python/prompt-toolkit/py2 | |
parent | bd5ef432f5cfb1e18851381329d94665a4c22470 (diff) | |
download | ydb-1aeb9a455974457866f78722ad98114bafc84e8a.tar.gz |
Restoring authorship annotation for Ivan Blinkov <ivan@blinkov.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/prompt-toolkit/py2')
87 files changed, 14983 insertions, 14983 deletions
diff --git a/contrib/python/prompt-toolkit/py2/LICENSE b/contrib/python/prompt-toolkit/py2/LICENSE index e1720e0fb7..4632fefd7f 100644 --- a/contrib/python/prompt-toolkit/py2/LICENSE +++ b/contrib/python/prompt-toolkit/py2/LICENSE @@ -1,27 +1,27 @@ -Copyright (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. +Copyright (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. diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/__init__.py index 6478ba4f9a..357270cbb0 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/__init__.py @@ -1,22 +1,22 @@ -""" -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 +""" +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, prompt_async - - + + # Don't forget to update in `docs/conf.py`! __version__ = '1.0.18' diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/application.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/application.py index 272d8bbcbb..ea6c485f9d 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/application.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/application.py @@ -1,126 +1,126 @@ -from __future__ import unicode_literals - -from .buffer import Buffer, AcceptAction -from .buffer_mapping import BufferMapping -from .clipboard import Clipboard, InMemoryClipboard +from __future__ import unicode_literals + +from .buffer import Buffer, AcceptAction +from .buffer_mapping import BufferMapping +from .clipboard import Clipboard, InMemoryClipboard from .enums import DEFAULT_BUFFER, EditingMode -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 .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.bindings.vi import load_vi_bindings from .key_binding.registry import BaseRegistry from .key_binding.defaults import load_key_bindings -from .layout import Window -from .layout.containers import Container -from .layout.controls import BufferControl -from .styles import DEFAULT_STYLE, Style +from .layout import Window +from .layout.containers import Container +from .layout.controls import BufferControl +from .styles import DEFAULT_STYLE, Style import six - -__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: + +__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.BaseRegistry` 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. + :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. :param erase_when_done: (bool) Clear the application output when it finishes. :param reverse_vi_search_direction: Normally, in Vi mode, a '/' searches forward and a '?' searches backward. In readline mode, this is usually reversed. - - 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. + + 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. :param editing_mode: :class:`~prompt_toolkit.enums.EditingMode`. - + Callbacks (all of these should accept a :class:`~prompt_toolkit.interface.CommandLineInterface` object as input.) - - :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. + + :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. :param on_render: Called right after rendering. :param on_invalidate: Called when the UI has been invalidated. - """ - 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, - + """ + 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, editing_mode=EditingMode.EMACS, erase_when_done=False, reverse_vi_search_direction=False, - - on_input_timeout=None, on_start=None, on_stop=None, + + on_input_timeout=None, on_start=None, on_stop=None, on_reset=None, on_initialize=None, on_buffer_changed=None, on_render=None, on_invalidate=None): - - paste_mode = to_cli_filter(paste_mode) - ignore_case = to_cli_filter(ignore_case) - mouse_support = to_cli_filter(mouse_support) + + paste_mode = to_cli_filter(paste_mode) + ignore_case = to_cli_filter(ignore_case) + mouse_support = to_cli_filter(mouse_support) reverse_vi_search_direction = to_cli_filter(reverse_vi_search_direction) - - 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 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, BaseRegistry) - 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 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 isinstance(editing_mode, six.string_types) assert on_input_timeout is None or callable(on_input_timeout) - assert style is None or isinstance(style, Style) + assert style is None or isinstance(style, Style) assert isinstance(erase_when_done, bool) - + assert on_start is None or callable(on_start) assert on_stop is None or callable(on_stop) assert on_reset is None or callable(on_reset) @@ -129,47 +129,47 @@ class Application(object): assert on_render is None or callable(on_render) assert on_invalidate is None or callable(on_invalidate) - self.layout = layout or Window(BufferControl()) - - # Make sure that the 'buffers' dictionary is a BufferMapping. + self.layout = layout or Window(BufferControl()) + + # Make sure that the 'buffers' dictionary is a BufferMapping. # NOTE: If no buffer is given, we create a default Buffer, with IGNORE as # default accept_action. This is what makes sense for most users # creating full screen applications. Doing nothing is the obvious # default. Those creating a REPL would use the shortcuts module that # passes in RETURN_DOCUMENT. self.buffer = buffer or Buffer(accept_action=AcceptAction.IGNORE) - 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: + 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 = load_key_bindings() - - 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 + + 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.editing_mode = editing_mode self.erase_when_done = erase_when_done self.reverse_vi_search_direction = reverse_vi_search_direction - + def dummy_handler(cli): " Dummy event handler. " diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/auto_suggest.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/auto_suggest.py index 1d5130521f..5e5eb80ef1 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/auto_suggest.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/auto_suggest.py @@ -1,88 +1,88 @@ -""" -`Fish-style <http://fishshell.com/>`_ 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) +""" +`Fish-style <http://fishshell.com/>`_ 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer.py index f5df289827..6a8b1dc4f5 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer.py @@ -1,106 +1,106 @@ -""" -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 +""" +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, PasteMode from .utils import Event from .cache import FastDictCache -from .validation import ValidationError - +from .validation import ValidationError + from six.moves import range -import os +import os import re import shlex -import six -import subprocess -import tempfile - -__all__ = ( - 'EditReadOnlyBuffer', - 'AcceptAction', - 'Buffer', - 'indent', - 'unindent', +import six +import subprocess +import tempfile + +__all__ = ( + 'EditReadOnlyBuffer', + 'AcceptAction', + 'Buffer', + 'indent', + 'unindent', 'reshape_text', -) - - -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. - """ - 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): +) + + +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. + """ + 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): # Set return value. - cli.set_return_value(buffer.document) - + cli.set_return_value(buffer.document) + # Make sure that if we run this UI again, that we reset this buffer, next # time. def reset_this_buffer(): buffer.reset() cli.pre_run_callables.append(reset_this_buffer) + - -AcceptAction.RETURN_DOCUMENT = AcceptAction(_return_document_handler) -AcceptAction.IGNORE = AcceptAction(handler=None) - - +AcceptAction.RETURN_DOCUMENT = AcceptAction(_return_document_handler) +AcceptAction.IGNORE = AcceptAction(handler=None) + + class ValidationState(object): " The validation state of a buffer. This is set after the validation. " VALID = 'VALID' @@ -108,63 +108,63 @@ class ValidationState(object): UNKNOWN = 'UNKNOWN' -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 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] + + _QUOTED_WORDS_RE = re.compile(r"""(\s+|".*?"|'.*?')""") @@ -183,113 +183,113 @@ class YankNthArgState(object): self.previous_inserted_word) -class Buffer(object): - """ - The core data structure that holds the text and cursor position of the - current input line and implements all text 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: - +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: When the buffer text changes. (Callable on None.) :param on_text_insert: When new text is inserted. (Callable on None.) :param on_cursor_position_changed: When the cursor moves. (Callable on None.) - - 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) + + 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 callable(on_text_changed) assert on_text_insert is None or callable(on_text_insert) assert on_cursor_position_changed is None or callable(on_cursor_position_changed) - - 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 - + + 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 + # Text width. (For wrapping, used by the Vi 'gq' operator.) self.text_width = 0 - #: The command buffer history. - # Note that we shouldn't use a lazy 'or' here. bool(history) could be - # False when empty. - self.history = InMemoryHistory() if history is None else history - - self.__cursor_position = 0 - - # Events + #: The command buffer history. + # Note that we shouldn't use a lazy 'or' here. bool(history) could be + # False when empty. + self.history = InMemoryHistory() if history is None else history + + self.__cursor_position = 0 + + # Events self.on_text_changed = Event(self, on_text_changed) self.on_text_insert = Event(self, on_text_insert) self.on_cursor_position_changed = Event(self, on_cursor_position_changed) - + # Document cache. (Avoid creating new Document instances.) self._document_cache = FastDictCache(Document, size=10) - 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 + 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 self.validation_state = ValidationState.UNKNOWN - - # State of the selection. - self.selection_state = None - + + # State of the selection. + self.selection_state = None + # Multiple cursor mode. (When we press 'I' or 'A' in visual-block mode, # we can insert text on multiple lines at once. This is implemented by # using multiple cursors.) @@ -298,9 +298,9 @@ class Buffer(object): # When doing consecutive up/down movements, prefer to stay at this column. self.preferred_column = None - # State of complete browser - self.complete_state = None # For interactive completion through Ctrl-N/Ctrl-P. - + # State of complete browser + self.complete_state = None # For interactive completion through Ctrl-N/Ctrl-P. + # State of Emacs yank-nth-arg completion. self.yank_nth_arg_state = None # for yank-nth-arg. @@ -308,36 +308,36 @@ class Buffer(object): # operation. This is used for rotating through the kill ring. self.document_before_paste = None - # Current suggestion. - self.suggestion = None - - # The history search text. (Used for filtering the history when we - # browse through it.) - self.history_search_text = None - - # Undo/redo stacks - self._undo_stack = [] # Stack of (text, cursor_position) - self._redo_stack = [] - - #: The working lines. Similar to history, except that this can be - #: modified. The user can press arrow_up and edit previous entries. - #: Ctrl-C should reset this, and copy the whole history back in here. - #: Enter should process the current command and append to the real - #: history. - self._working_lines = self.history.strings[:] - self._working_lines.append(initial_document.text) - self.__working_index = len(self._working_lines) - 1 - - # <getters/setters> - - def _set_text(self, value): - """ set text at current working_index. Return whether it changed. """ + # 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 + + # <getters/setters> + + def _set_text(self, value): + """ set text at current working_index. Return whether it changed. """ working_index = self.working_index working_lines = self._working_lines - + original_value = working_lines[working_index] working_lines[working_index] = value - + # Return True when this text has been changed. if len(value) != len(original_value): # For Python 2, it seems that when two strings have a different @@ -350,190 +350,190 @@ class Buffer(object): return True return False - def _set_cursor_position(self, value): - """ Set cursor position. Return whether it changed. """ - original_position = self.__cursor_position - self.__cursor_position = max(0, value) - - return value != original_position - - @property - def text(self): - return self._working_lines[self.working_index] - - @text.setter - def text(self, value): - """ - Setting text. (When doing this, make sure that the cursor_position is - valid for this text. text/cursor_position should be consistent at any time, - otherwise set a Document instead.) - """ - assert isinstance(value, six.text_type), 'Got %r' % value - 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 + 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.validation_state = ValidationState.UNKNOWN - self.complete_state = None + self.complete_state = None self.yank_nth_arg_state = None self.document_before_paste = None - self.selection_state = None - self.suggestion = None + self.selection_state = None + self.suggestion = None self.preferred_column = 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 + + # 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.validation_state = ValidationState.UNKNOWN - self.complete_state = None + self.complete_state = None self.yank_nth_arg_state = None self.document_before_paste = None - + # Unset preferred_column. (Will be set after the cursor movement, if # required.) self.preferred_column = None - # Note that the cursor position can change if we have a selection the - # new position of the cursor determines the end of the selection. - - # fire 'on_cursor_position_changed' event. - self.on_cursor_position_changed.fire() - - @property - def document(self): - """ - Return :class:`~prompt_toolkit.document.Document` instance from the + # Note that the cursor position can change if we have a selection the + # new position of the cursor determines the end of the selection. + + # fire 'on_cursor_position_changed' event. + self.on_cursor_position_changed.fire() + + @property + def document(self): + """ + Return :class:`~prompt_toolkit.document.Document` instance from the current text, cursor position and selection state. - """ + """ return self._document_cache[ self.text, self.cursor_position, self.selection_state] - - @document.setter - def document(self, value): - """ - Set :class:`~prompt_toolkit.document.Document` instance. - - This will set both the text and cursor position at the same time, but - atomically. (Change events will be triggered only after both have been set.) - """ - self.set_document(value) - - def set_document(self, value, bypass_readonly=False): - """ - Set :class:`~prompt_toolkit.document.Document` instance. Like the - ``document`` property, but accept an ``bypass_readonly`` argument. - - :param bypass_readonly: When True, don't raise an - :class:`.EditReadOnlyBuffer` exception, even - when the buffer is read-only. - """ - 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 <getters/setters> - - 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) - + + @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 <getters/setters> + + def save_to_undo_stack(self, clear_redo_stack=True): + """ + Safe current state (input text and cursor position), so that we can + restore it by calling undo. + """ + # Safe if the text is different from the text at the top of the stack + # is different. If the text is the same, just update the cursor position. + if self._undo_stack and self._undo_stack[-1][0] == self.text: + self._undo_stack[-1] = (self._undo_stack[-1][0], self.cursor_position) + else: + self._undo_stack.append((self.text, self.cursor_position)) + + # Saving anything to the undo stack, clears the redo stack. + if clear_redo_stack: + self._redo_stack = [] + + def transform_lines(self, line_index_iterator, transform_callback): + """ + Transforms the text on a range of lines. + When the iterator yield an index not in the range of lines that the + document contains, it skips them silently. + + To uppercase some lines:: + + new_text = transform_lines(range(5,10), lambda text: text.upper()) + + :param line_index_iterator: Iterator of line numbers (int) + :param transform_callback: callable that takes the original text of a + line, and return the new text for this line. + + :returns: The new text. + """ + # Split lines + lines = self.text.split('\n') + + # Apply transformation + for index in line_index_iterator: + try: + lines[index] = transform_callback(lines[index]) + except IndexError: + pass + + return '\n'.join(lines) + def transform_current_line(self, transform_callback): """ Apply the given transformation function to the current line. @@ -548,364 +548,364 @@ class Buffer(object): transform_callback(document.text[a:b]) + document.text[b:]) - def transform_region(self, from_, to, transform_callback): - """ - Transform a part of the input string. - - :param from_: (int) start position. - :param to: (int) end position. - :param transform_callback: Callable which accepts a string and returns - the transformed string. - """ - assert from_ < to - - self.text = ''.join([ - self.text[:from_] + - transform_callback(self.text[from_:to]) + - self.text[to:] - ]) - - def cursor_left(self, count=1): - self.cursor_position += self.document.get_cursor_left_position(count=count) - - def cursor_right(self, count=1): - self.cursor_position += self.document.get_cursor_right_position(count=count) - - def cursor_up(self, count=1): - """ (for multiline edit). Move cursor to the previous line. """ + def transform_region(self, from_, to, transform_callback): + """ + Transform a part of the input string. + + :param from_: (int) start position. + :param to: (int) end position. + :param transform_callback: Callable which accepts a string and returns + the transformed string. + """ + assert from_ < to + + self.text = ''.join([ + self.text[:from_] + + transform_callback(self.text[from_:to]) + + self.text[to:] + ]) + + def cursor_left(self, count=1): + self.cursor_position += self.document.get_cursor_left_position(count=count) + + def cursor_right(self, count=1): + self.cursor_position += self.document.get_cursor_right_position(count=count) + + def cursor_up(self, count=1): + """ (for multiline edit). Move cursor to the previous line. """ original_column = self.preferred_column or self.document.cursor_position_col self.cursor_position += self.document.get_cursor_up_position( count=count, preferred_column=original_column) - + # Remember the original column for the next up/down movement. self.preferred_column = original_column - def cursor_down(self, count=1): - """ (for multiline edit). Move cursor to the next line. """ + def cursor_down(self, count=1): + """ (for multiline edit). Move cursor to the next line. """ original_column = self.preferred_column or self.document.cursor_position_col self.cursor_position += self.document.get_cursor_down_position( count=count, preferred_column=original_column) - + # Remember the original column for the next up/down movement. self.preferred_column = original_column def auto_up(self, count=1, go_to_start_of_line_if_history_changes=False): - """ - If we're not on the first line (of a multiline input) go a line up, - otherwise go back in history. (If nothing is selected.) - """ - if self.complete_state: - self.complete_previous(count=count) - elif self.document.cursor_position_row > 0: + """ + If we're not on the first line (of a multiline input) go a line up, + otherwise go back in history. (If nothing is selected.) + """ + if self.complete_state: + self.complete_previous(count=count) + elif self.document.cursor_position_row > 0: self.cursor_up(count=count) - elif not self.selection_state: - self.history_backward(count=count) - + elif not self.selection_state: + self.history_backward(count=count) + # Go to the start of the line? if go_to_start_of_line_if_history_changes: self.cursor_position += self.document.get_start_of_line_position() def auto_down(self, count=1, go_to_start_of_line_if_history_changes=False): - """ - If we're not on the last line (of a multiline input) go a line down, - otherwise go forward in history. (If nothing is selected.) - """ - if self.complete_state: - self.complete_next(count=count) - elif self.document.cursor_position_row < self.document.line_count - 1: + """ + If we're not on the last line (of a multiline input) go a line down, + otherwise go forward in history. (If nothing is selected.) + """ + if self.complete_state: + self.complete_next(count=count) + elif self.document.cursor_position_row < self.document.line_count - 1: self.cursor_down(count=count) - elif not self.selection_state: - self.history_forward(count=count) - + elif not self.selection_state: + self.history_forward(count=count) + # Go to the start of the line? if go_to_start_of_line_if_history_changes: self.cursor_position += self.document.get_start_of_line_position() - def delete_before_cursor(self, count=1): - """ + def delete_before_cursor(self, count=1): + """ Delete specified number of characters before cursor and return the deleted text. - """ - assert count >= 0 - deleted = '' - - if self.cursor_position > 0: - deleted = self.text[self.cursor_position - count:self.cursor_position] - - new_text = self.text[:self.cursor_position - count] + self.text[self.cursor_position:] - new_cursor_position = self.cursor_position - len(deleted) - - # Set new Document atomically. - self.document = Document(new_text, new_cursor_position) - - return deleted - - def delete(self, count=1): - """ + """ + assert count >= 0 + deleted = '' + + if self.cursor_position > 0: + deleted = self.text[self.cursor_position - count:self.cursor_position] + + new_text = self.text[:self.cursor_position - count] + self.text[self.cursor_position:] + new_cursor_position = self.cursor_position - len(deleted) + + # Set new Document atomically. + self.document = Document(new_text, new_cursor_position) + + return deleted + + def delete(self, count=1): + """ Delete specified number of characters and Return the deleted text. - """ - if self.cursor_position < len(self.text): - deleted = self.document.text_after_cursor[:count] - self.text = self.text[:self.cursor_position] + \ - self.text[self.cursor_position + len(deleted):] - return deleted - else: - return '' - + """ + if self.cursor_position < len(self.text): + deleted = self.document.text_after_cursor[:count] + self.text = self.text[:self.cursor_position] + \ + self.text[self.cursor_position + len(deleted):] + return deleted + else: + return '' + def join_next_line(self, separator=' '): - """ - Join the next line to the current one by deleting the line ending after - the current line. - """ - if not self.document.on_last_line: - self.cursor_position += self.document.get_end_of_line_position() - self.delete() - - # Remove spaces. + """ + Join the next line to the current one by deleting the line ending after + the current line. + """ + if not self.document.on_last_line: + self.cursor_position += self.document.get_end_of_line_position() + self.delete() + + # Remove spaces. self.text = (self.document.text_before_cursor + separator + - self.document.text_after_cursor.lstrip(' ')) - + self.document.text_after_cursor.lstrip(' ')) + def join_selected_lines(self, separator=' '): - """ - Join the selected lines. - """ - assert self.selection_state - - # Get lines. - from_, to = sorted([self.cursor_position, self.selection_state.original_cursor_position]) - - before = self.text[:from_] - lines = self.text[from_:to].splitlines() - after = self.text[to:] - - # Replace leading spaces with just one space. + """ + Join the selected lines. + """ + assert self.selection_state + + # Get lines. + from_, to = sorted([self.cursor_position, self.selection_state.original_cursor_position]) + + before = self.text[:from_] + lines = self.text[from_:to].splitlines() + after = self.text[to:] + + # Replace leading spaces with just one space. lines = [l.lstrip(' ') + separator for l in lines] - - # Set new document. - self.document = Document(text=before + ''.join(lines) + after, - cursor_position=len(before + ''.join(lines[:-1])) - 1) - - def swap_characters_before_cursor(self): - """ - Swap the last two characters before the cursor. - """ - pos = self.cursor_position - - if pos >= 2: - a = self.text[pos - 2] - b = self.text[pos - 1] - - self.text = self.text[:pos-2] + b + a + self.text[pos:] - - def go_to_history(self, index): - """ - Go to this item in the history. - """ - if index < len(self._working_lines): - self.working_index = index - self.cursor_position = len(self.text) - - def complete_next(self, count=1, disable_wrap_around=False): - """ - Browse to the next completions. - (Does nothing if there are no completion.) - """ - if self.complete_state: - completions_count = len(self.complete_state.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: + + # 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.document.text_before_cursor - else: - self.history_search_text = None - - def _history_matches(self, i): - """ - True when the current entry matches the history search. - (when we don't have history search, it's also True.) - """ - return (self.history_search_text is None or - self._working_lines[i].startswith(self.history_search_text)) - - def history_forward(self, count=1): - """ - Move forwards through the history. - - :param count: Amount of items to move forward. - """ - self._set_history_search() - - # Go forward in history. - found_something = False - - for i in range(self.working_index + 1, len(self._working_lines)): - if self._history_matches(i): - self.working_index = i - count -= 1 - found_something = True - if count == 0: - break - - # If we found an entry, move cursor to the end of the first line. - if found_something: - self.cursor_position = 0 - self.cursor_position += self.document.get_end_of_line_position() - - def history_backward(self, count=1): - """ - Move backwards through history. - """ - self._set_history_search() - - # Go back in history. - found_something = False - - for i in range(self.working_index - 1, -1, -1): - if self._history_matches(i): - self.working_index = i - count -= 1 - found_something = True - if count == 0: - break - - # If we move to another entry, move cursor to the end of the line. - if found_something: - self.cursor_position = len(self.text) - + else: + self.history_search_text = None + + def _history_matches(self, i): + """ + True when the current entry matches the history search. + (when we don't have history search, it's also True.) + """ + return (self.history_search_text is None or + self._working_lines[i].startswith(self.history_search_text)) + + def history_forward(self, count=1): + """ + Move forwards through the history. + + :param count: Amount of items to move forward. + """ + self._set_history_search() + + # Go forward in history. + found_something = False + + for i in range(self.working_index + 1, len(self._working_lines)): + if self._history_matches(i): + self.working_index = i + count -= 1 + found_something = True + if count == 0: + break + + # If we found an entry, move cursor to the end of the first line. + if found_something: + self.cursor_position = 0 + self.cursor_position += self.document.get_end_of_line_position() + + def history_backward(self, count=1): + """ + Move backwards through history. + """ + self._set_history_search() + + # Go back in history. + found_something = False + + for i in range(self.working_index - 1, -1, -1): + if self._history_matches(i): + self.working_index = i + count -= 1 + found_something = True + if count == 0: + break + + # If we move to another entry, move cursor to the end of the line. + if found_something: + self.cursor_position = len(self.text) + def yank_nth_arg(self, n=None, _yank_last_arg=False): """ Pick nth word from previous history entry (depending on current @@ -963,36 +963,36 @@ class Buffer(object): """ self.yank_nth_arg(n=n, _yank_last_arg=True) - def start_selection(self, selection_type=SelectionType.CHARACTERS): - """ - Take the current cursor position as the start of this selection. - """ - self.selection_state = SelectionState(self.cursor_position, selection_type) - - def copy_selection(self, _cut=False): - """ - Copy selected text and return :class:`.ClipboardData` instance. - """ - 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 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, paste_mode=PasteMode.EMACS, count=1): - """ - Insert the data from the clipboard. - """ - assert isinstance(data, ClipboardData) + """ + Insert the data from the clipboard. + """ + assert isinstance(data, ClipboardData) assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) - + original_document = self.document self.document = self.document.paste_clipboard_data(data, paste_mode=paste_mode, count=count) @@ -1000,219 +1000,219 @@ class Buffer(object): # because assigning to 'document' will erase it. self.document_before_paste = original_document - def newline(self, copy_margin=True): - """ - Insert a line ending at the current position. - """ - if copy_margin: - self.insert_text('\n' + self.document.leading_whitespace_in_current_line) - else: - self.insert_text('\n') - - def insert_line_above(self, copy_margin=True): - """ - Insert a new line above the current one. - """ - if copy_margin: - insert = self.document.leading_whitespace_in_current_line + '\n' - else: - insert = '\n' - - self.cursor_position += self.document.get_start_of_line_position() - self.insert_text(insert) - self.cursor_position -= 1 - - def insert_line_below(self, copy_margin=True): - """ - Insert a new line below the current one. - """ - if copy_margin: - insert = '\n' + self.document.leading_whitespace_in_current_line - else: - insert = '\n' - - self.cursor_position += self.document.get_end_of_line_position() - self.insert_text(insert) - - def insert_text(self, data, overwrite=False, move_cursor=True, fire_event=True): - """ - Insert characters at cursor position. - - :param fire_event: Fire `on_text_insert` event. This is mainly used to - trigger autocompletion while typing. - """ + def newline(self, copy_margin=True): + """ + Insert a line ending at the current position. + """ + if copy_margin: + self.insert_text('\n' + self.document.leading_whitespace_in_current_line) + else: + self.insert_text('\n') + + def insert_line_above(self, copy_margin=True): + """ + Insert a new line above the current one. + """ + if copy_margin: + insert = self.document.leading_whitespace_in_current_line + '\n' + else: + insert = '\n' + + self.cursor_position += self.document.get_start_of_line_position() + self.insert_text(insert) + self.cursor_position -= 1 + + def insert_line_below(self, copy_margin=True): + """ + Insert a new line below the current one. + """ + if copy_margin: + insert = '\n' + self.document.leading_whitespace_in_current_line + else: + insert = '\n' + + self.cursor_position += self.document.get_end_of_line_position() + self.insert_text(insert) + + def insert_text(self, data, overwrite=False, move_cursor=True, fire_event=True): + """ + Insert characters at cursor position. + + :param fire_event: Fire `on_text_insert` event. This is mainly used to + trigger autocompletion while typing. + """ # Original text & cursor position. otext = self.text ocpos = self.cursor_position - # In insert/text mode. - if overwrite: + # In insert/text mode. + if overwrite: # Don't overwrite the newline itself. Just before the line ending, # it should act like insert mode. overwritten_text = otext[ocpos:ocpos + len(data)] - if '\n' in overwritten_text: - overwritten_text = overwritten_text[:overwritten_text.find('\n')] - + if '\n' in overwritten_text: + overwritten_text = overwritten_text[:overwritten_text.find('\n')] + self.text = otext[:ocpos] + data + otext[ocpos + len(overwritten_text):] - else: + else: self.text = otext[:ocpos] + data + otext[ocpos:] - - 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. - """ + + 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. + """ # Don't call the validator again, if it was already called for the # current input. if self.validation_state != ValidationState.UNKNOWN: return self.validation_state == ValidationState.VALID - - # 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)) - + + # 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_state = ValidationState.INVALID - self.validation_error = e - return False - + self.validation_error = e + return False + self.validation_state = ValidationState.VALID self.validation_error = None - 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 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 + else: + working_index, document = result + + return (working_index, document.cursor_position) + + def document_for_search(self, search_state): + """ + Return a :class:`~prompt_toolkit.document.Document` instance that has the text/cursor position for this search, if we would apply it. This will be used in the :class:`~prompt_toolkit.layout.controls.BufferControl` to display feedback while searching. - """ - search_result = self._search(search_state, include_current_position=True) - - if search_result is None: - return self.document - else: - working_index, cursor_position = search_result - + """ + search_result = self._search(search_state, include_current_position=True) + + if search_result is None: + return self.document + else: + working_index, cursor_position = search_result + # Keep selection, when `working_index` was not changed. if working_index == self.working_index: selection = self.selection_state @@ -1237,137 +1237,137 @@ class Buffer(object): working_index, cursor_position = search_result return cursor_position - def apply_search(self, search_state, include_current_position=True, count=1): - """ - Apply search. If something is found, set `working_index` and - `cursor_position`. - """ + 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 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 'VISUAL' or 'EDITOR' environment variable has been set, use that. - # Otherwise, fall back to the first available editor that we can find. + # Otherwise, fall back to the first available editor that we can find. visual = os.environ.get('VISUAL') - editor = os.environ.get('EDITOR') - - editors = [ + editor = os.environ.get('EDITOR') + + editors = [ visual, - editor, - - # Order of preference. - '/usr/bin/editor', - '/usr/bin/nano', - '/usr/bin/pico', - '/usr/bin/vi', - '/usr/bin/emacs', - ] - - for e in editors: - if e: - try: + editor, + + # Order of preference. + '/usr/bin/editor', + '/usr/bin/nano', + '/usr/bin/pico', + '/usr/bin/vi', + '/usr/bin/emacs', + ] + + for e in editors: + if e: + try: # Use 'shlex.split()', because $VISUAL can contain spaces # and quotes. returncode = subprocess.call(shlex.split(e) + [filename]) - return returncode == 0 - - except OSError: - # Executable does not exist, try the next one. - pass - - return False - - -def 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) + 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) def reshape_text(buffer, from_row, to_row): diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer_mapping.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer_mapping.py index 34f443bd47..99884ddd03 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer_mapping.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/buffer_mapping.py @@ -1,92 +1,92 @@ -""" -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.') +""" +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.') diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/__init__.py index 56202ddd3b..5c9cc8df31 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/__init__.py @@ -1,8 +1,8 @@ -from .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 +from .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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/base.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/base.py index 803c0b0e7d..71be6148eb 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/base.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/base.py @@ -1,62 +1,62 @@ -""" -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)) - +""" +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)) + def rotate(self): """ For Emacs mode, rotate the kill ring. """ - @abstractmethod - def get_data(self): - """ - Return clipboard data. - """ + @abstractmethod + def get_data(self): + """ + Return clipboard data. + """ diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/in_memory.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/in_memory.py index 081666ab80..a8591b91f9 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/in_memory.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/in_memory.py @@ -1,36 +1,36 @@ -from .base import Clipboard, ClipboardData - +from .base import Clipboard, ClipboardData + from collections import deque -__all__ = ( - 'InMemoryClipboard', -) - - -class InMemoryClipboard(Clipboard): - """ - Default clipboard implementation. - Just keep the data in memory. +__all__ = ( + 'InMemoryClipboard', +) + + +class InMemoryClipboard(Clipboard): + """ + Default clipboard implementation. + Just keep the data in memory. This implements a kill-ring, for Emacs mode. - """ + """ def __init__(self, data=None, max_size=60): assert data is None or isinstance(data, ClipboardData) assert max_size >= 1 - + self.max_size = max_size self._ring = deque() if data is not None: self.set_data(data) - def set_data(self, data): - assert isinstance(data, ClipboardData) + def set_data(self, data): + assert isinstance(data, ClipboardData) self._ring.appendleft(data) - + while len(self._ring) > self.max_size: self._ring.pop() - def get_data(self): + def get_data(self): if self._ring: return self._ring[0] else: diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/pyperclip.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/pyperclip.py index 61ab3aac0a..2dc49ae8cb 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/pyperclip.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/clipboard/pyperclip.py @@ -1,39 +1,39 @@ -from __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) +from __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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/completion.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/completion.py index 339738ab97..2d9897a204 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/completion.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/completion.py @@ -1,45 +1,45 @@ -""" -""" -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): +""" +""" +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): if self.display == self.text: return '%s(text=%r, start_position=%r)' % ( self.__class__.__name__, self.text, self.start_position) @@ -47,30 +47,30 @@ class Completion(object): return '%s(text=%r, start_position=%r, display=%r)' % ( self.__class__.__name__, self.text, self.start_position, self.display) - - def __eq__(self, other): - return ( - self.text == other.text and - self.start_position == other.start_position and - self.display == other.display and - self.display_meta == other.display_meta) - - def __hash__(self): - return hash((self.text, self.start_position, self.display, self.display_meta)) - - @property - def display_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 '' - + + 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 '' + def new_completion_from_position(self, position): """ (Only for internal use!) @@ -79,7 +79,7 @@ class Completion(object): after inserting the common prefix. """ assert isinstance(position, int) and position - self.start_position >= 0 - + return Completion( text=self.text[position - self.start_position:], display=self.display, @@ -87,84 +87,84 @@ class Completion(object): get_display_meta=self._get_display_meta) -class CompleteEvent(object): - """ - Event that called the completer. - - :param text_inserted: When True, it means that completions are requested - because of a text insert. (`Buffer.complete_while_typing`.) - :param completion_requested: When True, it means that the user 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) - +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) + completions2 = [c for c in completions if doesnt_change_before_cursor(c)] - + # When there is at least one completion that changes the text before the # cursor, don't return any common part. if len(completions2) != len(completions): return '' - # Return the common prefix. - def get_suffix(completion): - return completion.text[-completion.start_position:] - + # Return the common prefix. + def get_suffix(completion): + return completion.text[-completion.start_position:] + return _commonprefix([get_suffix(c) for c in completions2]) - - -def _commonprefix(strings): - # Similar to os.path.commonprefix - if not strings: - return '' - - else: - s1 = min(strings) - s2 = max(strings) - - for i, c in enumerate(s1): - if c != s2[i]: - return s1[:i] - - return s1 + + +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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/__init__.py index 43893b8c8c..a788d61004 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/__init__.py @@ -1,5 +1,5 @@ -from __future__ import unicode_literals - -from .filesystem import PathCompleter -from .base import WordCompleter -from .system import SystemCompleter +from __future__ import unicode_literals + +from .filesystem import PathCompleter +from .base import WordCompleter +from .system import SystemCompleter diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/base.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/base.py index 65a69fede1..0a4a3abc70 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/base.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/base.py @@ -1,61 +1,61 @@ -from __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) +from __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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/filesystem.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/filesystem.py index cbd74d8fea..287ec6e85d 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/filesystem.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/filesystem.py @@ -1,105 +1,105 @@ -from __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 += '/' +from __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 += '/' elif self.only_directories: continue - + if not self.file_filter(full_name): continue - yield Completion(completion, 0, display=filename) - except OSError: - pass - - -class ExecutableCompleter(PathCompleter): - """ - Complete only 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), + 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), diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/system.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/system.py index 76d6c1f9b4..35b2eeaef2 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/system.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/completers/system.py @@ -1,56 +1,56 @@ -from __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<executable>[^\s]+) - - # Ignore literals in between. - ( - \s+ - ("[^"]*" | '[^']*' | [^'"]+ ) - )* - - \s+ - - # Filename as parameters. - ( - (?P<filename>[^\s]+) | - "(?P<double_quoted_filename>[^\s]+)" | - '(?P<single_quoted_filename>[^\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), - }) +from __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<executable>[^\s]+) + + # Ignore literals in between. + ( + \s+ + ("[^"]*" | '[^']*' | [^'"]+ ) + )* + + \s+ + + # Filename as parameters. + ( + (?P<filename>[^\s]+) | + "(?P<double_quoted_filename>[^\s]+)" | + '(?P<single_quoted_filename>[^\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), + }) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/__init__.py index 314cb1f81d..21f2c55e75 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/__init__.py @@ -1,76 +1,76 @@ -r""" -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<varname>...)`` 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 +r""" +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<varname>...)`` 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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/compiler.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/compiler.py index 01476bf626..4b0939fbf8 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/compiler.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/compiler.py @@ -1,408 +1,408 @@ -r""" -Compiler for a regular grammar. - -Example usage:: - - # Create and compile grammar. - p = compile('add \s+ (?P<var1>[^\s]+) \s+ (?P<var2>[^\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<operator1>[^\s]+) \s+ (?P<var1>[^\s]+) \s+ (?P<var2>[^\s]+)) | - - # Operators with only one arguments. - ((?P<operator2>[^\s]+) \s+ (?P<var1>[^\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 - +r""" +Compiler for a regular grammar. + +Example usage:: + + # Create and compile grammar. + p = compile('add \s+ (?P<var1>[^\s]+) \s+ (?P<var2>[^\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<operator1>[^\s]+) \s+ (?P<var1>[^\s]+) \s+ (?P<var2>[^\s]+)) | + + # Operators with only one arguments. + ((?P<operator2>[^\s]+) \s+ (?P<var1>[^\s]+)) + ''') + + # Match partial input string. + m = p.match_prefix('add 23') + + # Get variables. (Notice that both operator1 and operator2 contain the + # value "add".) This is because our input is incomplete, and we don't know + # yet in which rule of the regex we we'll end up. It could also be that + # `operator1` and `operator2` have a different autocompleter and we want to + # call all possible autocompleters that would result in valid input.) + m.variables().get('var1') # Returns "23" + m.variables().get('operator1') # Returns "add" + m.variables().get('operator2') # Returns "add" + +""" +from __future__ import unicode_literals +import re + from six.moves import range -from .regex_parser import Any, 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('<trailing_input>', 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) +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('<trailing_input>', 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/completion.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/completion.py index bb49986a03..ff4aadca2c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/completion.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/completion.py @@ -1,84 +1,84 @@ -""" -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 +""" +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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/lexer.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/lexer.py index c166d84fd1..353c91c087 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/lexer.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/lexer.py @@ -1,57 +1,57 @@ -""" -`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 +""" +`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 prompt_toolkit.document import Document -from prompt_toolkit.layout.lexers import Lexer +from prompt_toolkit.layout.lexers import Lexer from prompt_toolkit.layout.utils import split_lines from prompt_toolkit.token import Token - -from .compiler import _CompiledGrammar + +from .compiler import _CompiledGrammar from six.moves import range - -__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 {} - + +__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: + 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: document = Document(text[v.start:v.stop]) lexer_tokens_for_line = lexer.lex_document(cli, document) lexer_tokens = [] @@ -61,22 +61,22 @@ class GrammarLexer(Lexer): if lexer_tokens: lexer_tokens.pop() - 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)] + 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)] def lex_document(self, cli, document): lines = list(split_lines(self._get_tokens(cli, document.text))) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/regex_parser.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/regex_parser.py index e5909b241e..5f903d30db 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/regex_parser.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/regex_parser.py @@ -1,262 +1,262 @@ -""" -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 +""" +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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/validation.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/validation.py index d5f8cfccc6..14ee901244 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/validation.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/regular_languages/validation.py @@ -1,57 +1,57 @@ -""" -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( +""" +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( cursor_position=v.start + e.cursor_position, - message=e.message) - else: - raise ValidationError(cursor_position=len(document.text), - message='Invalid command') + message=e.message) + else: + raise ValidationError(cursor_position=len(document.text), + message='Invalid command') diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/__init__.py index 7b7aeec820..815c9adf5e 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/__init__.py @@ -1,2 +1,2 @@ -from .server import * -from .application import * +from .server import * +from .application import * diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/application.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/application.py index 7fe6cc97b7..e499f29997 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/application.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/application.py @@ -1,32 +1,32 @@ -""" -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. - """ +""" +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. + """ diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/log.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/log.py index 10792ceed6..8d97ec9ac7 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/log.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/log.py @@ -1,11 +1,11 @@ -""" -Python logger for the telnet server. -""" -from __future__ import unicode_literals -import logging - -logger = logging.getLogger(__package__) - -__all__ = ( - 'logger', -) +""" +Python logger for the telnet server. +""" +from __future__ import unicode_literals +import logging + +logger = logging.getLogger(__package__) + +__all__ = ( + 'logger', +) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/protocol.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/protocol.py index b1bb0ccbe8..2774160121 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/protocol.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/protocol.py @@ -1,181 +1,181 @@ -""" -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)) +""" +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)) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/server.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/server.py index d75a9572eb..d8f8a087eb 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/server.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/telnet/server.py @@ -1,407 +1,407 @@ -""" -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) +""" +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, write_binary=False) - - # 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) + + # 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/validators/base.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/validators/base.py index 16c1539c52..3a71e98c8b 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/validators/base.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/contrib/validators/base.py @@ -1,34 +1,34 @@ -from __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: +from __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 = set([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 - + + 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(cursor_position=index, message=self.error_message) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py index 25d817ddd0..68d6829bad 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/document.py @@ -1,41 +1,41 @@ -""" +""" The `Document` that implements all the text operations/querying. -""" -from __future__ import unicode_literals - +""" +from __future__ import unicode_literals + import bisect -import re -import six -import string +import re +import six +import string import weakref from six.moves import range, map - + from .selection import SelectionType, SelectionState, PasteMode -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*)') - +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*)') + # Share the Document._cache between all Document instances. # (Document instances are considered immutable. That means that if another # `Document` is constructed with the same text, it should have the same # `_DocumentCache`.) _text_to_document_cache = weakref.WeakValueDictionary() # Maps document.text to DocumentCache instance. - + class _ImmutableLineList(list): """ @@ -65,42 +65,42 @@ class _DocumentCache(object): self.line_indexes = None -class Document(object): - """ - This is a immutable class around the text and cursor position, and contains - methods for querying this data, e.g. to give the text before the cursor. - - This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer` - object, and accessed as the `document` property of that class. - - :param text: string - :param cursor_position: int - :param selection: :class:`.SelectionState` - """ +class Document(object): + """ + This is a immutable class around the text and cursor position, and contains + methods for querying this data, e.g. to give the text before the cursor. + + This class is usually instantiated by a :class:`~prompt_toolkit.buffer.Buffer` + object, and accessed as the `document` property of that class. + + :param text: string + :param cursor_position: int + :param selection: :class:`.SelectionState` + """ __slots__ = ('_text', '_cursor_position', '_selection', '_cache') - - def __init__(self, text='', cursor_position=None, selection=None): - assert isinstance(text, six.text_type), 'Got %r' % text - assert selection is None or isinstance(selection, SelectionState) - - # Check cursor position. It can also be right after the end. (Where we - # insert text.) - assert cursor_position is None or cursor_position <= len(text), AssertionError( - 'cursor_position=%r, len_text=%r' % (cursor_position, len(text))) - - # By default, if no cursor position was given, make sure to put the - # cursor position is at the end of the document. This is what makes - # sense in most places. - if cursor_position is None: - cursor_position = len(text) - + + def __init__(self, text='', cursor_position=None, selection=None): + assert isinstance(text, six.text_type), 'Got %r' % text + assert selection is None or isinstance(selection, SelectionState) + + # Check cursor position. It can also be right after the end. (Where we + # insert text.) + assert cursor_position is None or cursor_position <= len(text), AssertionError( + 'cursor_position=%r, len_text=%r' % (cursor_position, len(text))) + + # By default, if no cursor position was given, make sure to put the + # cursor position is at the end of the document. This is what makes + # sense in most places. + if cursor_position is None: + cursor_position = len(text) + # Keep these attributes private. A `Document` really has to be # considered to be immutable, because otherwise the caching will break # things. Because of that, we wrap these into read-only properties. self._text = text self._cursor_position = cursor_position self._selection = selection - + # Cache for lines/indexes. (Shared with other Document instances that # contain the same text. try: @@ -115,10 +115,10 @@ class Document(object): # self._cache = _text_to_document_cache.setdefault(self.text, _DocumentCache()) # assert self._cache - def __repr__(self): - return '%s(%r, %r)' % (self.__class__.__name__, self.text, self.cursor_position) - - @property + def __repr__(self): + return '%s(%r, %r)' % (self.__class__.__name__, self.text, self.cursor_position) + + @property def text(self): " The document text. " return self._text @@ -134,47 +134,47 @@ class Document(object): return self._selection @property - def current_char(self): - """ Return character under cursor or an empty string. """ - return self._get_char_relative_to_cursor(0) or '' - - @property - def char_before_cursor(self): - """ Return character before the cursor or an empty string. """ - return self._get_char_relative_to_cursor(-1) or '' - - @property - def text_before_cursor(self): - return self.text[:self.cursor_position:] - - @property - def text_after_cursor(self): - return self.text[self.cursor_position:] - - @property - def current_line_before_cursor(self): - """ Text from the start of the line until the cursor. """ + def current_char(self): + """ Return character under cursor or an empty string. """ + return self._get_char_relative_to_cursor(0) or '' + + @property + def char_before_cursor(self): + """ Return character before the cursor or an empty string. """ + return self._get_char_relative_to_cursor(-1) or '' + + @property + def text_before_cursor(self): + return self.text[:self.cursor_position:] + + @property + def text_after_cursor(self): + return self.text[self.cursor_position:] + + @property + def current_line_before_cursor(self): + """ Text from the start of the line until the cursor. """ _, _, text = self.text_before_cursor.rpartition('\n') return text - - @property - def current_line_after_cursor(self): - """ Text from the cursor until the end of the line. """ + + @property + def current_line_after_cursor(self): + """ Text from the cursor until the end of the line. """ text, _, _ = self.text_after_cursor.partition('\n') return text - - @property - def lines(self): + + @property + def lines(self): """ Array of all the lines. """ - # Cache, because this one is reused very often. + # Cache, because this one is reused very often. if self._cache.lines is None: self._cache.lines = _ImmutableLineList(self.text.split('\n')) - + return self._cache.lines - - @property + + @property def _line_start_indexes(self): """ Array pointing to the start indexes of all the lines. @@ -203,75 +203,75 @@ class Document(object): return self._cache.line_indexes @property - def lines_from_current(self): - """ - Array of the lines starting from the current line, until the last line. - """ - return self.lines[self.cursor_position_row:] - - @property - def line_count(self): - r""" Return the number of lines in this document. If the document ends - with a trailing \n, that counts as the beginning of a new line. """ - return len(self.lines) - - @property - def current_line(self): - """ Return the text on the line where the cursor is. (when the input - consists of just one line, it equals `text`. """ - return self.current_line_before_cursor + self.current_line_after_cursor - - @property - def leading_whitespace_in_current_line(self): - """ The leading whitespace in the left margin of the current line. """ - current_line = self.current_line - length = len(current_line) - len(current_line.lstrip()) - return current_line[:length] - - def _get_char_relative_to_cursor(self, offset=0): - """ - Return character relative to cursor position, or empty string - """ - try: - return self.text[self.cursor_position + offset] - except IndexError: - return '' - - @property - def on_first_line(self): - """ - True when we are at the first line. - """ - return self.cursor_position_row == 0 - - @property - def on_last_line(self): - """ - True when we are at the last line. - """ - return self.cursor_position_row == self.line_count - 1 - - @property - def cursor_position_row(self): - """ - Current row. (0-based.) - """ + def lines_from_current(self): + """ + Array of the lines starting from the current line, until the last line. + """ + return self.lines[self.cursor_position_row:] + + @property + def line_count(self): + r""" Return the number of lines in this document. If the document ends + with a trailing \n, that counts as the beginning of a new line. """ + return len(self.lines) + + @property + def current_line(self): + """ Return the text on the line where the cursor is. (when the input + consists of just one line, it equals `text`. """ + return self.current_line_before_cursor + self.current_line_after_cursor + + @property + def leading_whitespace_in_current_line(self): + """ The leading whitespace in the left margin of the current line. """ + current_line = self.current_line + length = len(current_line) - len(current_line.lstrip()) + return current_line[:length] + + def _get_char_relative_to_cursor(self, offset=0): + """ + Return character relative to cursor position, or empty string + """ + try: + return self.text[self.cursor_position + offset] + except IndexError: + return '' + + @property + def on_first_line(self): + """ + True when we are at the first line. + """ + return self.cursor_position_row == 0 + + @property + def on_last_line(self): + """ + True when we are at the last line. + """ + return self.cursor_position_row == self.line_count - 1 + + @property + def cursor_position_row(self): + """ + Current row. (0-based.) + """ row, _ = self._find_line_start_index(self.cursor_position) return row - - @property - def cursor_position_col(self): - """ - Current column. (0-based.) - """ + + @property + def cursor_position_col(self): + """ + Current column. (0-based.) + """ # (Don't use self.text_before_cursor to calculate this. Creating # substrings and doing rsplit is too expensive for getting the cursor # position.) _, line_start_index = self._find_line_start_index(self.cursor_position) return self.cursor_position - line_start_index - + def _find_line_start_index(self, index): - """ + """ For the index of a character at a certain line, calculate the index of the first character on that line. @@ -284,23 +284,23 @@ class Document(object): def translate_index_to_position(self, index): """ - Given an index for the text, return the corresponding (row, col) tuple. - (0-based. Returns (0, 0) for index=0.) - """ + Given an index for the text, return the corresponding (row, col) tuple. + (0-based. Returns (0, 0) for index=0.) + """ # Find start of this line. row, row_index = self._find_line_start_index(index) col = index - row_index + + return row, col + - 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.) + def translate_row_col_to_index(self, row, col): + """ + Given a (row, col) tuple, return the corresponding index. + (Row and col params are 0-based.) Negative row/col values are turned into zero. - """ + """ try: result = self._line_start_indexes[row] line = self.lines[row] @@ -311,239 +311,239 @@ class Document(object): else: result = self._line_start_indexes[-1] line = self.lines[-1] - + result += max(0, min(col, len(line))) - # Keep in range. (len(self.text) is included, because the cursor can be - # right after the end of the text as well.) - result = max(0, min(result, len(self.text))) - return result - - @property - def is_cursor_at_the_end(self): - """ True when the cursor is at the end of the text. """ - return self.cursor_position == len(self.text) - - @property - def is_cursor_at_the_end_of_line(self): - """ True when the cursor is at the end of this line. """ + # Keep in range. (len(self.text) is included, because the cursor can be + # right after the end of the text as well.) + result = max(0, min(result, len(self.text))) + return result + + @property + def is_cursor_at_the_end(self): + """ True when the cursor is at the end of the text. """ + return self.cursor_position == len(self.text) + + @property + def is_cursor_at_the_end_of_line(self): + """ True when the cursor is at the end of this line. """ return self.current_char in ('\n', '') - - def has_match_at_current_position(self, sub): - """ - `True` when this substring is found at the cursor position. - """ + + def has_match_at_current_position(self, sub): + """ + `True` when this substring is found at the cursor position. + """ return self.text.find(sub, self.cursor_position) == self.cursor_position - - def find(self, sub, in_current_line=False, include_current_position=False, + + 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. - """ + """ + 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. + """ if count < 0: return self.find_previous_word_beginning(count=-count, WORD=WORD) - regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE - iterator = regex.finditer(self.text_after_cursor) - - try: - for i, match in enumerate(iterator): - # Take first match, unless it's the word on which we're right now. - if i == 0 and match.start(1) == 0: - count += 1 - - if i + 1 == count: - return match.start(1) - except StopIteration: - pass - - def find_next_word_ending(self, include_current_position=False, count=1, WORD=False): - """ - Return an index relative to the cursor position pointing to the end - of the next word. Return `None` if nothing was found. - """ + regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE + iterator = regex.finditer(self.text_after_cursor) + + try: + for i, match in enumerate(iterator): + # Take first match, unless it's the word on which we're right now. + if i == 0 and match.start(1) == 0: + count += 1 + + if i + 1 == count: + return match.start(1) + except StopIteration: + pass + + def find_next_word_ending(self, include_current_position=False, count=1, WORD=False): + """ + Return an index relative to the cursor position pointing to the end + of the next word. Return `None` if nothing was found. + """ if count < 0: return self.find_previous_word_ending(count=-count, WORD=WORD) - if include_current_position: - text = self.text_after_cursor - else: - text = self.text_after_cursor[1:] - - regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE - iterable = regex.finditer(text) - - try: - for i, match in enumerate(iterable): - if i + 1 == count: - value = match.end(1) - - if include_current_position: - return value - else: - return value + 1 - - except StopIteration: - pass - - def find_previous_word_beginning(self, count=1, WORD=False): - """ - Return an index relative to the cursor position pointing to the start + if include_current_position: + text = self.text_after_cursor + else: + text = self.text_after_cursor[1:] + + regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE + iterable = regex.finditer(text) + + try: + for i, match in enumerate(iterable): + if i + 1 == count: + value = match.end(1) + + if include_current_position: + return value + else: + return value + 1 + + except StopIteration: + pass + + def find_previous_word_beginning(self, count=1, WORD=False): + """ + Return an index relative to the cursor position pointing to the start of the previous word. Return `None` if nothing was found. - """ + """ if count < 0: return self.find_next_word_beginning(count=-count, WORD=WORD) - regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE - iterator = regex.finditer(self.text_before_cursor[::-1]) - - try: - for i, match in enumerate(iterator): - if i + 1 == count: - return - match.end(1) - except StopIteration: - pass - + regex = _FIND_BIG_WORD_RE if WORD else _FIND_WORD_RE + iterator = regex.finditer(self.text_before_cursor[::-1]) + + try: + for i, match in enumerate(iterator): + if i + 1 == count: + return - match.end(1) + except StopIteration: + pass + def find_previous_word_ending(self, count=1, WORD=False): """ Return an index relative to the cursor position pointing to the end @@ -568,93 +568,93 @@ class Document(object): 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. - """ + def find_next_matching_line(self, match_func, count=1): + """ + Look downwards for empty lines. + Return the line index, relative to the current line. + """ + result = None + + for index, line in enumerate(self.lines[self.cursor_position_row + 1:]): + if match_func(line): + result = 1 + index + count -= 1 + + if count == 0: + break + + return result + + def find_previous_matching_line(self, match_func, count=1): + """ + Look upwards for empty lines. + Return the line index, relative to the current line. + """ + result = None + + for index, line in enumerate(self.lines[:self.cursor_position_row][::-1]): + if match_func(line): + result = -1 - index + count -= 1 + + if count == 0: + break + + return result + + def get_cursor_left_position(self, count=1): + """ + Relative position for cursor left. + """ if count < 0: return self.get_cursor_right_position(-count) - return - min(self.cursor_position_col, count) - - def get_cursor_right_position(self, count=1): - """ - Relative position for cursor_right. - """ + return - min(self.cursor_position_col, count) + + def get_cursor_right_position(self, count=1): + """ + Relative position for cursor_right. + """ if count < 0: return self.get_cursor_left_position(-count) - return min(count, len(self.current_line_after_cursor)) - + return min(count, len(self.current_line_after_cursor)) + def get_cursor_up_position(self, count=1, preferred_column=None): - """ - Return the relative cursor position (character index) where we would be if the - user pressed the arrow-up button. + """ + Return the relative cursor position (character index) where we would be if the + user pressed the arrow-up button. :param preferred_column: When given, go to this column instead of staying at the current column. - """ - assert count >= 1 + """ + assert count >= 1 column = self.cursor_position_col if preferred_column is None else preferred_column - + return self.translate_row_col_to_index( max(0, self.cursor_position_row - count), column) - self.cursor_position - + def get_cursor_down_position(self, count=1, preferred_column=None): """ Return the relative cursor position (character index) where we would be if the user pressed the arrow-down button. - + :param preferred_column: When given, go to this column instead of staying at the current column. """ assert count >= 1 column = self.cursor_position_col if preferred_column is None else preferred_column - + return self.translate_row_col_to_index( self.cursor_position_row + count, column) - self.cursor_position - + def find_enclosing_bracket_right(self, left_ch, right_ch, end_pos=None): """ Find the right bracket enclosing current position. Return the relative position to the cursor position. - + When `end_pos` is given, don't look past the position. - """ + """ if self.current_char == right_ch: return 0 @@ -678,137 +678,137 @@ class Document(object): return i - self.cursor_position def find_enclosing_bracket_left(self, left_ch, right_ch, start_pos=None): - """ + """ Find the left bracket enclosing current position. Return the relative position to the cursor position. - + When `start_pos` is given, don't look past the position. """ if self.current_char == left_ch: return 0 - + if start_pos is None: start_pos = 0 else: start_pos = max(0, start_pos) - + stack = 1 - + # Look backward. for i in range(self.cursor_position - 1, start_pos - 1, -1): c = self.text[i] - + if c == right_ch: stack += 1 elif c == left_ch: stack -= 1 - + if stack == 0: return i - self.cursor_position - + def find_matching_bracket_position(self, start_pos=None, end_pos=None): - """ - Return relative cursor position of matching [, (, { or < bracket. + """ + Return relative cursor position of matching [, (, { or < bracket. When `start_pos` or `end_pos` are given. Don't look past the positions. - """ - + """ + # Look for a match. - for A, B in '()', '[]', '{}', '<>': - if self.current_char == A: + for A, B in '()', '[]', '{}', '<>': + if self.current_char == A: return self.find_enclosing_bracket_right(A, B, end_pos=end_pos) or 0 - elif self.current_char == B: + elif self.current_char == B: return self.find_enclosing_bracket_left(A, B, start_pos=start_pos) or 0 - - return 0 - - def get_start_of_document_position(self): - """ Relative position for the start of the document. """ - return - self.cursor_position - - def get_end_of_document_position(self): - """ Relative position for the end of the document. """ - return len(self.text) - self.cursor_position - - def get_start_of_line_position(self, after_whitespace=False): - """ Relative position for the start of this line. """ - if after_whitespace: - current_line = self.current_line - return len(current_line) - len(current_line.lstrip()) - self.cursor_position_col - else: - return - len(self.current_line_before_cursor) - - def get_end_of_line_position(self): - """ Relative position for the end of this line. """ - return len(self.current_line_after_cursor) - - def last_non_blank_of_current_line_position(self): - """ - Relative position for the last non blank character of this line. - """ + + return 0 + + def get_start_of_document_position(self): + """ Relative position for the start of the document. """ + return - self.cursor_position + + def get_end_of_document_position(self): + """ Relative position for the end of the document. """ + return len(self.text) - self.cursor_position + + def get_start_of_line_position(self, after_whitespace=False): + """ Relative position for the start of this line. """ + if after_whitespace: + current_line = self.current_line + return len(current_line) - len(current_line.lstrip()) - self.cursor_position_col + else: + return - len(self.current_line_before_cursor) + + def get_end_of_line_position(self): + """ Relative position for the end of this line. """ + return len(self.current_line_after_cursor) + + def last_non_blank_of_current_line_position(self): + """ + Relative position for the last non blank character of this line. + """ return len(self.current_line.rstrip()) - self.cursor_position_col - 1 - - def get_column_cursor_position(self, column): - """ - Return the relative cursor position for this column at the current - line. (It will stay between the boundaries of the line in case of a - larger number.) - """ - line_length = len(self.current_line) - current_column = self.cursor_position_col - column = max(0, min(line_length, column)) - - return column - current_column - + + def get_column_cursor_position(self, column): + """ + Return the relative cursor position for this column at the current + line. (It will stay between the boundaries of the line in case of a + larger number.) + """ + line_length = len(self.current_line) + current_column = self.cursor_position_col + column = max(0, min(line_length, column)) + + return column - current_column + def selection_range(self): # XXX: shouldn't this return `None` if there is no selection??? - """ - Return (from, to) tuple of the selection. - start and end position are included. - - This doesn't take the selection type into account. Use - `selection_ranges` instead. - """ - if self.selection: - from_, to = sorted([self.cursor_position, self.selection.original_cursor_position]) - else: - from_, to = self.cursor_position, self.cursor_position - - return from_, to - - def selection_ranges(self): - """ - Return a list of (from, to) tuples for the selection or none if nothing - was selected. 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: + """ + 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.rfind('\n', 0, from_) + 1) - + if self.text.find('\n', to) >= 0: to = self.text.find('\n', to) - else: + else: to = len(self.text) - 1 - - yield from_, to - + + yield from_, to + def selection_range_at_line(self, row): """ If the selection spans a portion of the given line, return a (from, to) tuple. @@ -840,107 +840,107 @@ class Document(object): return from_column, to_column - def cut_selection(self): - """ - Return a (:class:`.Document`, :class:`.ClipboardData`) tuple, where the - document represents the new document when the selection is cut, and the - clipboard data, represents whatever has to be put on the clipboard. - """ - if self.selection: - cut_parts = [] - remaining_parts = [] - new_cursor_position = self.cursor_position - - last_to = 0 - for from_, to in self.selection_ranges(): - if last_to == 0: - new_cursor_position = from_ - - remaining_parts.append(self.text[last_to:from_]) - cut_parts.append(self.text[from_:to + 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 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, paste_mode=PasteMode.EMACS, count=1): - """ - Return a new :class:`.Document` instance which contains the result if - we would paste this data at the current cursor position. - + """ + Return a new :class:`.Document` instance which contains the result if + we would paste this data at the current cursor position. + :param paste_mode: Where to paste. (Before/after/emacs.) - :param count: When >1, Paste multiple times. - """ - assert isinstance(data, ClipboardData) + :param count: When >1, Paste multiple times. + """ + assert isinstance(data, ClipboardData) assert paste_mode in (PasteMode.VI_BEFORE, PasteMode.VI_AFTER, PasteMode.EMACS) - + before = (paste_mode == PasteMode.VI_BEFORE) after = (paste_mode == PasteMode.VI_AFTER) - if data.type == SelectionType.CHARACTERS: + if data.type == SelectionType.CHARACTERS: if after: - new_text = (self.text[:self.cursor_position + 1] + data.text * count + - self.text[self.cursor_position + 1:]) + new_text = (self.text[:self.cursor_position + 1] + data.text * count + + self.text[self.cursor_position + 1:]) else: new_text = self.text_before_cursor + data.text * count + self.text_after_cursor - + new_cursor_position = self.cursor_position + len(data.text) * count if before: new_cursor_position -= 1 - elif data.type == SelectionType.LINES: - l = self.cursor_position_row - if before: - lines = self.lines[:l] + [data.text] * count + self.lines[l:] - new_text = '\n'.join(lines) - new_cursor_position = len(''.join(self.lines[:l])) + l - else: - lines = self.lines[:l + 1] + [data.text] * count + self.lines[l + 1:] - new_cursor_position = len(''.join(self.lines[:l + 1])) + l + 1 - new_text = '\n'.join(lines) - - elif data.type == SelectionType.BLOCK: + 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 - + start_line = self.cursor_position_row + start_column = self.cursor_position_col + (0 if before else 1) + + for i, line in enumerate(data.text.split('\n')): + index = i + start_line + if index >= len(lines): + lines.append('') + + lines[index] = lines[index].ljust(start_column) + lines[index] = lines[index][:start_column] + line * count + lines[index][start_column:] + + new_text = '\n'.join(lines) + new_cursor_position = self.cursor_position + (0 if before else 1) + + return Document(text=new_text, cursor_position=new_cursor_position) + + def empty_line_count_at_the_end(self): + """ + Return number of empty lines at the end of the document. + """ + count = 0 + for line in self.lines[::-1]: + if not line or line.isspace(): + count += 1 + else: + break + + return count + def start_of_paragraph(self, count=1, before=False): """ Return the start of the current paragraph. (Relative cursor position.) @@ -971,31 +971,31 @@ class Document(object): else: return len(self.text_after_cursor) - # Modifiers. - - def insert_after(self, text): - """ - Create a new document, with this text inserted after the buffer. - It keeps selection ranges and cursor position in sync. - """ - return Document( - text=self.text + text, - cursor_position=self.cursor_position, - selection=self.selection) - - def insert_before(self, text): - """ - Create a new document, with this text inserted before the buffer. - It keeps selection ranges and cursor position in sync. - """ - selection_state = self.selection - - if selection_state: - selection_state = SelectionState( - original_cursor_position=selection_state.original_cursor_position + len(text), - type=selection_state.type) - - return Document( - text=text + self.text, - cursor_position=self.cursor_position + len(text), - selection=selection_state) + # 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/enums.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/enums.py index 6945f44c96..a45060f27a 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/enums.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/enums.py @@ -1,29 +1,29 @@ -from __future__ import unicode_literals - - -class IncrementalSearchDirection(object): - FORWARD = 'FORWARD' - BACKWARD = 'BACKWARD' - - +from __future__ import unicode_literals + + +class IncrementalSearchDirection(object): + FORWARD = 'FORWARD' + BACKWARD = 'BACKWARD' + + class EditingMode(object): # The set of key bindings that is active. VI = 'VI' EMACS = 'EMACS' -#: Name of the search buffer. -SEARCH_BUFFER = 'SEARCH_BUFFER' - -#: Name of the default buffer. -DEFAULT_BUFFER = 'DEFAULT_BUFFER' - -#: Name of the system buffer. -SYSTEM_BUFFER = 'SYSTEM_BUFFER' - -# 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' +#: 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' diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_base.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_base.py index ace2b8db49..1ea41dda60 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_base.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_base.py @@ -1,46 +1,46 @@ -""" -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 +""" +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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_posix.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_posix.py index 426ed96f67..26606e8493 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_posix.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_posix.py @@ -1,113 +1,113 @@ -""" -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 - +""" +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(loop=self.loop) - - @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. + + @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(loop=self.loop) - - # 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() - + + # 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() + # Quit when the input stream was closed. if stdin_reader.closed: self.stop() - 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) + 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_win32.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_win32.py index 45f5f52679..adc538afe9 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_win32.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/asyncio_win32.py @@ -1,83 +1,83 @@ -""" -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 - +""" +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 + self._console_input_reader.close() - 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) + 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/base.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/base.py index db86face66..0be339ca7c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/base.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/base.py @@ -1,85 +1,85 @@ -from __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``. - +from __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 `time.time` value. 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.) + use. If the eventloop is saturated, consider this task to be low + priority and postpone maximum until this timestamp. (For instance, + repaint is done using low priority.) Note: In the past, this used to be a datetime.datetime instance, but apparently, executing `time.time` is more efficient: it does fewer system calls. (It doesn't read /etc/localtime.) - """ + """ diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/callbacks.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/callbacks.py index 04adab6fd4..068d900f62 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/callbacks.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/callbacks.py @@ -1,29 +1,29 @@ -from __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 +from __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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/inputhook.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/inputhook.py index bab1f4c003..c49d5876a1 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/inputhook.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/inputhook.py @@ -1,80 +1,80 @@ -""" -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 +""" +Similar to `PyOS_InputHook` of the Python API. Some eventloops can have an +inputhook to allow easy integration with other event loops. + +When the eventloop of prompt-toolkit is idle, it can call such a hook. This +hook can call another eventloop that runs for a short while, for instance to +keep a graphical user interface responsive. + +It's the responsibility of this hook to exit when there is input ready. +There are two ways to detect when input is ready: + +- Call the `input_is_ready` method periodically. Quit when this returns `True`. + +- Add the `fileno` as a watch to the external eventloop. Quit when file descriptor + becomes readable. (But don't read from it.) + + Note that this is not the same as checking for `sys.stdin.fileno()`. The + eventloop of prompt-toolkit allows thread-based executors, for example for + asynchronous autocompletion. When the completion for instance is ready, we + also want prompt-toolkit to gain control again in order to display that. + +An alternative to using input hooks, is to create a custom `EventLoop` class that +controls everything. +""" +from __future__ import unicode_literals +import os +import threading from prompt_toolkit.utils import is_windows from .select import select_fds - -__all__ = ( - 'InputHookContext', -) - - -class InputHookContext(object): - """ - Given as a parameter to the inputhook. - """ - def __init__(self, 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: + +__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: # Before calling 'os.read', call select.select. This is required # when the gevent monkey patch has been applied. 'os.read' is never # monkey patched and won't be cooperative, so that would block all @@ -88,20 +88,20 @@ class InputHookContext(object): if not is_windows(): select_fds([self._r], timeout=None) - os.read(self._r, 1024) - except OSError: - # This happens when the window resizes and a SIGWINCH was received. - # We get 'Error: [Errno 4] Interrupted system call' - # Just ignore. - pass - self._input_is_ready = None - - def close(self): - """ - Clean up resources. - """ - if self._r: - os.close(self._r) - os.close(self._w) - - self._r = self._w = None + 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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py index f631dbd891..a2ef2ac3d8 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix.py @@ -1,244 +1,244 @@ -from __future__ import unicode_literals -import fcntl -import os -import signal -import threading +from __future__ import unicode_literals +import fcntl +import os +import signal +import threading import time - -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 + +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 from .select import AutoSelector, Selector, fd_to_int - -__all__ = ( - 'PosixEventLoop', -) - + +__all__ = ( + 'PosixEventLoop', +) + _now = time.time - - -class PosixEventLoop(EventLoop): - """ - Event loop for posix systems (Linux, Mac os X). - """ + + +class PosixEventLoop(EventLoop): + """ + Event loop for posix systems (Linux, Mac os X). + """ def __init__(self, inputhook=None, selector=AutoSelector): - assert inputhook is None or callable(inputhook) + assert inputhook is None or callable(inputhook) assert issubclass(selector, Selector) - - self.running = False - self.closed = False - self._running = False - self._callbacks = None - - self._calls_from_executor = [] - self._read_fds = {} # Maps fd to handler. + + self.running = False + self.closed = False + self._running = False + self._callbacks = None + + self._calls_from_executor = [] + self._read_fds = {} # Maps fd to handler. self.selector = selector() - - # 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 - + + # 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 + # Quit when the input stream was closed. if stdin_reader.closed: self.stop() - self.add_reader(stdin, read_from_stdin) - self.add_reader(self._schedule_pipe[0], None) - - with ctx: - while self._running: - # Call inputhook. + self.add_reader(stdin, read_from_stdin) + self.add_reader(self._schedule_pipe[0], None) + + with ctx: + while self._running: + # Call inputhook. if self._inputhook_context: with TimeIt() as inputhook_timer: - 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) + 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) inputhook_duration = inputhook_timer.duration else: inputhook_duration = 0 - - # Calculate remaining timeout. (The inputhook consumed some of the time.) - if current_timeout[0] is None: - remaining_timeout = None - else: + + # 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_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 = [] + + # 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 = None # Lazy load time. (Fewer system calls.) - - for fd in fds: - # For the 'call_from_executor' fd, put each pending - # item on either the high or low priority queue. - if fd == self._schedule_pipe[0]: - for c, max_postpone_until in self._calls_from_executor: + + 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: # Execute now. - tasks.append(c) - else: + tasks.append(c) + else: # Execute soon, if `max_postpone_until` is in the future. now = now or _now() if max_postpone_until < now: tasks.append(c) else: low_priority_tasks.append((c, max_postpone_until)) - 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) - - # 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. - """ + 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) + + # 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. + """ fds = self.selector.select(timeout) return fds - - 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``. - + + 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 `time.time` value. 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.) - """ + use. If the eventloop is saturated, consider this task to be low + priority and postpone maximum until this timestamp. (For instance, + repaint is done using low priority.) + """ assert _max_postpone_until is None or isinstance(_max_postpone_until, float) - self._calls_from_executor.append((callback, _max_postpone_until)) - - if self._schedule_pipe: + self._calls_from_executor.append((callback, _max_postpone_until)) + + if self._schedule_pipe: try: os.write(self._schedule_pipe[1], b'x') except (AttributeError, IndexError, OSError): @@ -247,60 +247,60 @@ class PosixEventLoop(EventLoop): # - We catch `OSError` (actually BrokenPipeError), because the # main thread could have closed the pipe already. pass - - 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. " + + 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. " fd = fd_to_int(fd) - self._read_fds[fd] = callback + self._read_fds[fd] = callback self.selector.register(fd) - - def remove_reader(self, fd): - " Remove read file descriptor from the event loop. " + + def remove_reader(self, fd): + " Remove read file descriptor from the event loop. " fd = fd_to_int(fd) - if fd in self._read_fds: - del self._read_fds[fd] - + if fd in self._read_fds: + del self._read_fds[fd] + self.selector.unregister(fd) - - -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) + + +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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix_utils.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix_utils.py index 320df438ca..af040650f0 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix_utils.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/posix_utils.py @@ -1,18 +1,18 @@ -from __future__ import unicode_literals - -from codecs import getincrementaldecoder -import os +from __future__ import unicode_literals + +from codecs import getincrementaldecoder +import os import six - -__all__ = ( - 'PosixStdinReader', -) - - -class PosixStdinReader(object): - """ - Wrapper around stdin which reads (nonblocking) the next available 1024 - bytes and decodes it. + +__all__ = ( + 'PosixStdinReader', +) + + +class PosixStdinReader(object): + """ + Wrapper around stdin which reads (nonblocking) the next available 1024 + bytes and decodes it. Note that you can't be sure that the input file is closed if the ``read`` function returns an empty string. When ``errors=ignore`` is passed, @@ -29,54 +29,54 @@ class PosixStdinReader(object): unrecognised bytes to the key bindings. Some terminals, like lxterminal and Guake, use the 'Mxx' notation to send mouse events, where each 'x' can be any possible byte. - """ + """ # By default, we want to 'ignore' errors here. The input stream can be full # of junk. One occurrence of this that I had was when using iTerm2 on OS X, # with "Option as Meta" checked (You should choose "Option as +Esc".) def __init__(self, stdin_fd, errors=('ignore' if six.PY2 else 'surrogateescape')): - assert isinstance(stdin_fd, int) - self.stdin_fd = stdin_fd + assert isinstance(stdin_fd, int) + self.stdin_fd = stdin_fd self.errors = errors - - # Create incremental decoder for decoding stdin. - # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because - # it could be that we are in the middle of a utf-8 byte sequence. - self._stdin_decoder_cls = getincrementaldecoder('utf-8') + + # Create incremental decoder for decoding stdin. + # We can not just do `os.read(stdin.fileno(), 1024).decode('utf-8')`, because + # it could be that we are in the middle of a utf-8 byte sequence. + self._stdin_decoder_cls = getincrementaldecoder('utf-8') self._stdin_decoder = self._stdin_decoder_cls(errors=errors) - + #: True when there is nothing anymore to read. self.closed = False - def read(self, count=1024): - # By default we choose a rather small chunk size, because reading - # big amounts of input at once, causes the event loop to process - # all these key bindings also at once without going back to the - # loop. This will make the application feel unresponsive. - """ - Read the input and return it as a string. + def read(self, count=1024): + # By default we choose a rather small chunk size, because reading + # big amounts of input at once, causes the event loop to process + # all these key bindings also at once without going back to the + # loop. This will make the application feel unresponsive. + """ + Read the input and return it as a string. Return the text. Note that this can return an empty string, even when the input stream was not yet closed. This means that something went wrong during the decoding. - """ + """ if self.closed: return b'' - # 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) + # Note: the following works better than wrapping `self.stdin` like + # `codecs.getreader('utf-8')(stdin)` and doing `read(1)`. + # Somehow that causes some latency when the escape + # character is pressed. (Especially on combination with the `select`.) + try: + data = os.read(self.stdin_fd, count) # Nothing more to read, stream is closed. if data == b'': self.closed = True return '' - except OSError: - # In case of SIGWINCH - data = b'' - + except OSError: + # In case of SIGWINCH + data = b'' + return self._stdin_decoder.decode(data) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/utils.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/utils.py index ff3a4cfd69..56b4646d1c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/utils.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/utils.py @@ -1,23 +1,23 @@ -from __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 +from __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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/win32.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/win32.py index 18e356f088..273c277de9 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/win32.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/eventloop/win32.py @@ -1,187 +1,187 @@ -""" -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 - +""" +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 msvcrt -import threading - -__all__ = ( - 'Win32EventLoop', -) - -WAIT_TIMEOUT = 0x00000102 -INPUT_TIMEOUT_MS = int(1000 * INPUT_TIMEOUT) - - -class Win32EventLoop(EventLoop): - """ - Event loop for Windows systems. +import threading + +__all__ = ( + 'Win32EventLoop', +) + +WAIT_TIMEOUT = 0x00000102 +INPUT_TIMEOUT_MS = int(1000 * INPUT_TIMEOUT) + + +class Win32EventLoop(EventLoop): + """ + Event loop for Windows systems. :param recognize_paste: When True, try to discover paste actions and turn the event into a BracketedPaste. - """ + """ def __init__(self, inputhook=None, recognize_paste=True): - assert inputhook is None or callable(inputhook) - + assert inputhook is None or callable(inputhook) + self._event = HANDLE(_create_event()) self._console_input_reader = ConsoleInputReader(recognize_paste=recognize_paste) - self._calls_from_executor = [] - - self.closed = False - self._running = False - + self._calls_from_executor = [] + + self.closed = False + self._running = False + # Additional readers. self._read_fds = {} # Maps fd to handler. - # 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) - + # 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.value: - # 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 - + # 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.value: - # When the Windows Event has been trigger, process the messages in the queue. - windll.kernel32.ResetEvent(self._event) - self._process_queued_calls_from_executor() - + # When the Windows Event has been trigger, process the messages in the queue. + windll.kernel32.ResetEvent(self._event) + self._process_queued_calls_from_executor() + elif handle in self._read_fds: callback = self._read_fds[handle] callback() - 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. - """ + 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. + """ handles = [self._event, self._console_input_reader.handle] handles.extend(self._read_fds.keys()) return _wait_for_handles(handles, 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 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() + self._console_input_reader.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. " + 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. " h = msvcrt.get_osfhandle(fd) self._read_fds[h] = callback - - def remove_reader(self, fd): - " Stop watching the file descriptor for read availability. " + + def remove_reader(self, fd): + " Stop watching the file descriptor for read availability. " h = msvcrt.get_osfhandle(fd) if h in self._read_fds: del self._read_fds[h] - - -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) + + +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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/__init__.py index d3f14efc1c..2f57dbd680 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/__init__.py @@ -1,36 +1,36 @@ -""" -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 * +""" +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 * diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/base.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/base.py index 6a1a1d0b10..7bbdd43498 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/base.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/base.py @@ -1,79 +1,79 @@ -from __future__ import unicode_literals -from abc import ABCMeta, abstractmethod -from six import with_metaclass - +from __future__ import unicode_literals +from abc import ABCMeta, abstractmethod +from six import with_metaclass + from prompt_toolkit.utils import test_callable_args - - -__all__ = ( - 'Filter', - 'Never', - 'Always', - 'Condition', -) - - -class Filter(with_metaclass(ABCMeta, object)): - """ - 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. - """ + + +__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. + """ return _and_cache[self, other] - - def __or__(self, other): - """ - Chaining of filters using the | operator. - """ + + def __or__(self, other): + """ + Chaining of filters using the | operator. + """ return _or_cache[self, other] - - def __invert__(self): - """ - Inverting of filters using the ~ operator. - """ + + def __invert__(self): + """ + Inverting of filters using the ~ operator. + """ return _invert_cache[self] - - def __bool__(self): - """ - By purpose, we don't allow bool(...) operations directly on a filter, - because 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 __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 test_args(self, *args): - """ + """ Test whether this filter can be called with the following argument list. - """ + """ return test_callable_args(self.__call__, args) - - -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): + + +class _AndCache(dict): + """ + Cache for And operation between filters. + (Filter classes are stateless, so we can reuse them.) + + Note: This could be a memory leak if we keep creating filters at runtime. + If that is True, the filters should be weakreffed (not the tuple of + filters), and tuples should be removed when one of these filters is + removed. In practise however, there is a finite amount of filters. + """ + def __missing__(self, filters): a, b = filters assert isinstance(b, Filter), 'Expecting filter, got %r' % b @@ -82,14 +82,14 @@ class _AndCache(dict): elif isinstance(b, Never) or isinstance(a, Always): return b - result = _AndList(filters) - self[filters] = result - return result - - -class _OrCache(dict): - """ Cache for Or operation between filters. """ - def __missing__(self, filters): + result = _AndList(filters) + self[filters] = result + return result + + +class _OrCache(dict): + """ Cache for Or operation between filters. """ + def __missing__(self, filters): a, b = filters assert isinstance(b, Filter), 'Expecting filter, got %r' % b @@ -98,11 +98,11 @@ class _OrCache(dict): elif isinstance(b, Never) or isinstance(a, Always): return a - result = _OrList(filters) - self[filters] = result - return result - - + result = _OrList(filters) + self[filters] = result + return result + + class _InvertCache(dict): """ Cache for inversion operator. """ def __missing__(self, filter): @@ -111,124 +111,124 @@ class _InvertCache(dict): return result -_and_cache = _AndCache() -_or_cache = _OrCache() +_and_cache = _AndCache() +_or_cache = _OrCache() _invert_cache = _InvertCache() - - -class _AndList(Filter): - """ - Result of &-operation between several filters. - """ - def __init__(self, filters): - all_filters = [] - - for f in filters: - if isinstance(f, _AndList): # Turn nested _AndLists into one. - all_filters.extend(f.filters) - else: - all_filters.append(f) - - self.filters = all_filters - + + +class _AndList(Filter): + """ + Result of &-operation between several filters. + """ + def __init__(self, filters): + all_filters = [] + + for f in filters: + if isinstance(f, _AndList): # Turn nested _AndLists into one. + all_filters.extend(f.filters) + else: + all_filters.append(f) + + self.filters = all_filters + def test_args(self, *args): return all(f.test_args(*args) for f in self.filters) - - 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) - - self.filters = all_filters - + + 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) + + self.filters = all_filters + def test_args(self, *args): return all(f.test_args(*args) for f in self.filters) - - 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 __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 test_args(self, *args): return self.filter.test_args(*args) - - -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 - + + +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 test_args(self, *a): return test_callable_args(self.func, a) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/cli.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/cli.py index c0b07317be..76adc8ef4b 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/cli.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/cli.py @@ -1,26 +1,26 @@ -""" -Filters that accept a `CommandLineInterface` as argument. -""" -from __future__ import unicode_literals -from .base import Filter +""" +Filters that accept a `CommandLineInterface` as argument. +""" +from __future__ import unicode_literals +from .base import Filter from prompt_toolkit.enums import EditingMode from prompt_toolkit.key_binding.vi_state import InputMode as ViInputMode from prompt_toolkit.cache import memoized - -__all__ = ( - 'HasArg', - 'HasCompletions', - 'HasFocus', - 'InFocusStack', - 'HasSearch', - 'HasSelection', - 'HasValidationError', - 'IsAborting', - 'IsDone', - 'IsMultiline', - 'IsReadOnly', - 'IsReturning', - 'RendererHeightIsKnown', + +__all__ = ( + 'HasArg', + 'HasCompletions', + 'HasFocus', + 'InFocusStack', + 'HasSearch', + 'HasSelection', + 'HasValidationError', + 'IsAborting', + 'IsDone', + 'IsMultiline', + 'IsReadOnly', + 'IsReturning', + 'RendererHeightIsKnown', 'InEditingMode', # Vi modes. @@ -37,197 +37,197 @@ __all__ = ( 'EmacsMode', 'EmacsInsertMode', 'EmacsSelectionMode', -) - - +) + + @memoized() -class HasFocus(Filter): - """ - Enable when this buffer has the focus. - """ - def __init__(self, buffer_name): +class HasFocus(Filter): + """ + Enable when this buffer has the focus. + """ + def __init__(self, buffer_name): self._buffer_name = buffer_name - + @property def buffer_name(self): " The given buffer name. (Read-only) " return self._buffer_name - def __call__(self, cli): - return cli.current_buffer_name == self.buffer_name - - def __repr__(self): - return 'HasFocus(%r)' % self.buffer_name - - + def __call__(self, cli): + return cli.current_buffer_name == self.buffer_name + + def __repr__(self): + return 'HasFocus(%r)' % self.buffer_name + + @memoized() -class InFocusStack(Filter): - """ - Enable when this buffer appears on the focus stack. - """ - def __init__(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 - + @property def buffer_name(self): " The given buffer name. (Read-only) " return self._buffer_name - def __call__(self, cli): - return self.buffer_name in cli.buffers.focus_stack - - def __repr__(self): - return 'InFocusStack(%r)' % self.buffer_name - - + def __call__(self, cli): + return self.buffer_name in cli.buffers.focus_stack + + def __repr__(self): + return 'InFocusStack(%r)' % self.buffer_name + + @memoized() -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 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()' + + @memoized() -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 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()' + + @memoized() -class IsMultiline(Filter): - """ - Enable in multiline mode. - """ - def __call__(self, cli): - return cli.current_buffer.is_multiline() - - def __repr__(self): - return 'IsMultiline()' - - +class IsMultiline(Filter): + """ + Enable in multiline mode. + """ + def __call__(self, cli): + return cli.current_buffer.is_multiline() + + def __repr__(self): + return 'IsMultiline()' + + @memoized() -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 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()' + + @memoized() -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 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()' + + @memoized() -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 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()' + + @memoized() -class HasSearch(Filter): - """ - Incremental search is active. - """ - def __call__(self, cli): - return cli.is_searching - - def __repr__(self): - return 'HasSearch()' - - +class HasSearch(Filter): + """ + Incremental search is active. + """ + def __call__(self, cli): + return cli.is_searching + + def __repr__(self): + return 'HasSearch()' + + @memoized() -class IsReturning(Filter): - """ - When a return value has been set. - """ - def __call__(self, cli): - return cli.is_returning - - def __repr__(self): - return 'IsReturning()' - - +class IsReturning(Filter): + """ + When a return value has been set. + """ + def __call__(self, cli): + return cli.is_returning + + def __repr__(self): + return 'IsReturning()' + + @memoized() -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 IsAborting(Filter): + """ + True when aborting. (E.g. Control-C pressed.) + """ + def __call__(self, cli): + return cli.is_aborting + + def __repr__(self): + return 'IsAborting()' + + @memoized() -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 IsExiting(Filter): + """ + True when exiting. (E.g. Control-D pressed.) + """ + def __call__(self, cli): + return cli.is_exiting + + def __repr__(self): + return 'IsExiting()' + + @memoized() -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 IsDone(Filter): + """ + True when the CLI is returning, aborting or exiting. + """ + def __call__(self, cli): + return cli.is_done + + def __repr__(self): + return 'IsDone()' + + @memoized() -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()' +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()' @memoized() diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/types.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/types.py index 3e89c39c01..caf600bb9b 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/types.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/types.py @@ -1,55 +1,55 @@ -from __future__ import unicode_literals -from six import with_metaclass +from __future__ import unicode_literals +from six import with_metaclass from collections import defaultdict import weakref - -__all__ = ( - 'CLIFilter', - 'SimpleFilter', -) - + +__all__ = ( + 'CLIFilter', + 'SimpleFilter', +) + # Cache for _FilterTypeMeta. (Don't test the same __instancecheck__ twice as # long as the object lives. -- We do this a lot and calling 'test_args' is # expensive.) _instance_check_cache = defaultdict(weakref.WeakKeyDictionary) -class _FilterTypeMeta(type): - def __instancecheck__(cls, instance): +class _FilterTypeMeta(type): + def __instancecheck__(cls, instance): cache = _instance_check_cache[tuple(cls.arguments_list)] - + def get(): " The actual test. " if not hasattr(instance, 'test_args'): return False return instance.test_args(*cls.arguments_list) - + try: return cache[instance] except KeyError: result = get() cache[instance] = result return result - - -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 = [] + + +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 = [] diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/utils.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/utils.py index 836d2956e7..b3a66df2c5 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/utils.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/filters/utils.py @@ -1,39 +1,39 @@ -from __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. - """ +from __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. + """ if not isinstance(bool_or_filter, (bool, SimpleFilter)): raise 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. - """ + + 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. + """ if not isinstance(bool_or_filter, (bool, CLIFilter)): raise 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) + + return { + True: _always, + False: _never, + }.get(bool_or_filter, bool_or_filter) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/history.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/history.py index d1eb5f2730..ab971ee05c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/history.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/history.py @@ -1,120 +1,120 @@ -from __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: +from __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: def write(t): f.write(t.encode('utf-8')) - - write('\n# %s\n' % datetime.datetime.now()) - for line in string.split('\n'): - write('+%s\n' % line) - - def __getitem__(self, key): - return self.strings[key] - - def __iter__(self): - return iter(self.strings) - - def __len__(self): - return len(self.strings) + + 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/input.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/input.py index f123732560..ad6d4cf696 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/input.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/input.py @@ -1,68 +1,68 @@ -""" -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 - +""" +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 io -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 - +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 + # The input object should be a TTY. assert self.stdin.isatty() @@ -78,54 +78,54 @@ class StdinInput(Input): else: raise io.UnsupportedOperation('Stdin is not a terminal.') - 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 __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_text(self, data): " Send text to the input. " - os.write(self._w, data.encode('utf-8')) - + os.write(self._w, data.encode('utf-8')) + # Deprecated alias for `send_text`. send = send_text - def raw_mode(self): - return DummyContext() - - def cooked_mode(self): - return DummyContext() + def raw_mode(self): + return DummyContext() + + def cooked_mode(self): + return DummyContext() def close(self): " Close pipe fds. " diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/interface.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/interface.py index e1e0e56393..0632fa4c1a 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/interface.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/interface.py @@ -1,89 +1,89 @@ -""" -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 +""" +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 time import types -import weakref - +import weakref + from subprocess import Popen -from .application import Application, AbortAction -from .buffer import Buffer -from .buffer_mapping import BufferMapping +from .application import Application, AbortAction +from .buffer import Buffer +from .buffer_mapping import BufferMapping from .completion import CompleteEvent, 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 .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 .key_binding.input_processor import KeyPress from .key_binding.registry import Registry from .key_binding.vi_state import ViState from .keys import Keys -from .output import Output -from .renderer import Renderer, print_tokens -from .search_state import SearchState +from .output import Output +from .renderer import Renderer, print_tokens +from .search_state import SearchState from .utils import Event - -# 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 + +# 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. The easiest way to create an eventloop is by calling :meth:`~prompt_toolkit.shortcuts.create_eventloop`. - :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) + :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 isinstance(eventloop, EventLoop), 'Passing an eventloop is required.' - assert output is None or isinstance(output, Output) - assert input is None or isinstance(input, Input) - + assert output is None or isinstance(output, Output) + assert input is None or isinstance(input, Input) + from .shortcuts import create_output - - self.application = application + + self.application = application self.eventloop = 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 - + 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 + #: EditingMode.VI or EditingMode.EMACS self.editing_mode = application.editing_mode @@ -93,38 +93,38 @@ class CommandLineInterface(object): #: Vi state. (For Vi key bindings.) self.vi_state = ViState() - #: 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 - - #: 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) - + #: 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 + + #: 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) + # Events. self.on_buffer_changed = Event(self, application.on_buffer_changed) self.on_initialize = Event(self, application.on_initialize) @@ -134,59 +134,59 @@ class CommandLineInterface(object): self.on_reset = Event(self, application.on_reset) self.on_start = Event(self, application.on_start) self.on_stop = Event(self, application.on_stop) - - # Trigger initialize callback. + + # Trigger initialize callback. self.reset() self.on_initialize += self.application.on_initialize self.on_initialize.fire() - - @property - def layout(self): - return self.application.layout - - @property - def clipboard(self): - return self.application.clipboard - + + @property + def layout(self): + return self.application.layout + + @property + def clipboard(self): + return self.application.clipboard + @property def pre_run_callables(self): return self.application.pre_run_callables - 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 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() - - return on_text_insert - - buffer.on_text_insert += create_on_insert_handler() - + # 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() + + return on_text_insert + + buffer.on_text_insert += create_on_insert_handler() + def buffer_changed(_): """ When the text in a buffer changes. @@ -199,140 +199,140 @@ class CommandLineInterface(object): buffer.on_text_changed += buffer_changed - 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. - + 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: XXX: not used anymore. The reason for having this option in the past was when this CommandLineInterface is run multiple times, that we could reset the buffer content from the previous run. This is now handled in the AcceptAction. - """ - # 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() + """ + # 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() self.vi_state.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. + + # 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.on_reset.fire() - - @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). + + @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). # Usually with the high priority, in order to make the application # feel responsive, but this can be tuned by changing the value of # `max_render_postpone_time`. @@ -340,38 +340,38 @@ class CommandLineInterface(object): _max_postpone_until = time.time() + self.max_render_postpone_time else: _max_postpone_until = None - + 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) - + # 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) + # Fire render event. self.on_render.fire() - 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. + def _on_resize(self): + """ + When the window size changes, we erase the current output and request + again the cursor position. When the CPR answer arrives, the output is + drawn again. + """ + # Erase, request position (when cursor is at the start position) + # and redraw again. -- The order is important. self.renderer.erase(leave_alternate_screen=False, erase_title=False) - self.renderer.request_absolute_cursor_position() - self._redraw() - + self.renderer.request_absolute_cursor_position() + self._redraw() + def _load_next_buffer_indexes(self): for buff, index in self._next_buffer_indexes.items(): if buff in self.buffers: @@ -388,34 +388,34 @@ class CommandLineInterface(object): del self.pre_run_callables[:] def run(self, reset_current_buffer=False, pre_run=None): - """ - Read input from the command line. - This runs the eventloop until a return value has been set. - + """ + Read input from the command line. + This runs the eventloop until a return value has been set. + :param reset_current_buffer: XXX: Not used anymore. - :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 - + :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.on_start.fire() self.reset() - - # Call pre_run. + + # Call pre_run. self._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.) + + # 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.) # If exit/abort haven't been called set, but another exception was # thrown instead for some reason, make sure that we redraw in exit @@ -424,48 +424,48 @@ class CommandLineInterface(object): self._exit_flag = True self._redraw() - self.renderer.reset() + self.renderer.reset() self.on_stop.fire() - 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. - """ + 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. + """ # Inline import, because it slows down startup when asyncio is not # needed. import asyncio - + @asyncio.coroutine def run(): assert pre_run is None or callable(pre_run) - + try: self._is_running = True - + self.on_start.fire() self.reset() - + # Call pre_run. self._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: if not self.is_done: @@ -477,181 +477,181 @@ class CommandLineInterface(object): self._is_running = False return run() - ''')) - 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 - + ''')) + 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, erase_when_done=False, _from_application_generator=False): # `erase_when_done` is deprecated, set Application.erase_when_done instead. - """ - 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. + """ + 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. if not _from_application_generator: 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() + + # 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() if erase_when_done or application.erase_when_done: sub_cli.renderer.erase() - sub_cli.renderer.reset() - sub_cli._is_running = False # Don't render anymore. - - self._sub_cli = None - - # Restore main application. + sub_cli.renderer.reset() + sub_cli._is_running = False # Don't render anymore. + + self._sub_cli = None + + # Restore main application. if not _from_application_generator: 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() - + + # 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, cooked_mode=True): - """ - 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. + """ + 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. :param cooked_mode: When True (the default), switch the input to cooked mode while executing the function. - - :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() + + :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() self._return_value = None - - # Run system command. + + # Run system command. if cooked_mode: with self.input.cooked_mode(): result = func() else: - result = func() - - # Redraw interface again. - self.renderer.reset() - self.renderer.request_absolute_cursor_position() - self._redraw() - - return result - + result = func() + + # Redraw interface again. + self.renderer.reset() + self.renderer.request_absolute_cursor_position() + self._redraw() + + return result + def run_application_generator(self, coroutine, render_cli_done=False): """ EXPERIMENTAL @@ -709,13 +709,13 @@ class CommandLineInterface(object): # Start processing coroutine. step_next() - 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_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 wait_for_enter(): """ Create a sub application to wait for the enter key press. @@ -737,10 +737,10 @@ class CommandLineInterface(object): key_bindings_registry=registry) self.run_sub_application(application) - def run(): + def run(): # Try to use the same input/output file descriptors as the one, # used to run this application. - try: + try: input_fd = self.input.fileno() except AttributeError: input_fd = sys.stdin.fileno() @@ -748,7 +748,7 @@ class CommandLineInterface(object): output_fd = self.output.fileno() except AttributeError: output_fd = sys.stdout.fileno() - + # Run sub process. # XXX: This will still block the event loop. p = Popen(command, shell=True, @@ -758,23 +758,23 @@ class CommandLineInterface(object): # Wait for the user to press enter. wait_for_enter() - self.run_in_terminal(run) - + self.run_in_terminal(run) + def suspend_to_background(self, suspend_group=True): - """ - (Not thread safe -- to be called from inside the key bindings.) - Suspend process. + """ + (Not thread safe -- to be called from inside the key bindings.) + Suspend process. :param suspend_group: When true, suspend the whole process group. (This is the default, and probably what you want.) - """ - # Only suspend when the opperating system supports it. - # (Not on Windows.) - if hasattr(signal, 'SIGTSTP'): - def run(): - # Send `SIGSTP` to own process. - # This will cause it to suspend. - + """ + # 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. + # Usually we want the whole process group to be suspended. This # handles the case when input is piped from another process. if suspend_group: @@ -782,59 +782,59 @@ class CommandLineInterface(object): else: 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. - + 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 completion_does_nothing(document, completion): """ Return `True` if applying this completion doesn't have any effect. @@ -845,32 +845,32 @@ class CommandLineInterface(object): len(text_before_cursor) + completion.start_position:] return replaced_text == completion.text - 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)) - - 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. - """ + 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)) + + 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. + """ complete_thread_running[0] = False # When there is only one completion, which has nothing to add, ignore it. @@ -878,21 +878,21 @@ class CommandLineInterface(object): completion_does_nothing(document, completions[0])): del completions[:] - # 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 - + # 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 common 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: + # is a common part. + if insert_common_part: + common_part = get_common_complete_suffix(document, completions) + if common_part: # Insert the common part, update completions. - buffer.insert_text(common_part) + buffer.insert_text(common_part) if len(completions) > 1: # (Don't call `async_completer` again, but # recalculate completions. See: @@ -902,289 +902,289 @@ class CommandLineInterface(object): for c in completions] else: 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 + else: + # When we were asked to insert the "common" + # prefix, but there was no common suffix but + # still exactly one match, then select the + # first. (It could be that we have a completion # which does * expansion, like '*.py', with - # exactly one match.) - if len(completions) == 1: - 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() + # 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() elif not buffer.complete_state: - # 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) - - def callback(): + # 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) + + def callback(): suggest_thread_running[0] = False - # 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) - + # 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, patch_stdout=True, patch_stderr=True): - """ - 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 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. :param patch_stdout: Replace `sys.stdout`. :param patch_stderr: Replace `sys.stderr`. - """ + """ return _PatchStdoutContext( self.stdout_proxy(raw=raw), patch_stdout=patch_stdout, patch_stderr=patch_stderr) - - 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 + + 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.on_input_timeout.fire() - - def feed_key(self, key_press): - """ - Feed a key press to the CommandLineInterface. - """ + + def feed_key(self, key_press): + """ + Feed a key press to the CommandLineInterface. + """ assert isinstance(key_press, KeyPress) cli = self._active_cli - # Feed the key and redraw. + # Feed the key and redraw. # (When the CLI is in 'done' state, it should return to the event loop # as soon as possible. Ignore all key presses beyond this point.) if not cli.is_done: cli.input_processor.feed(key_press) cli.input_processor.process_keys() - - -class _PatchStdoutContext(object): + + +class _PatchStdoutContext(object): def __init__(self, new_stdout, patch_stdout=True, patch_stderr=True): - self.new_stdout = new_stdout + self.new_stdout = new_stdout self.patch_stdout = patch_stdout self.patch_stderr = patch_stderr - - def __enter__(self): - self.original_stdout = sys.stdout + + def __enter__(self): + self.original_stdout = sys.stdout self.original_stderr = sys.stderr - + if self.patch_stdout: sys.stdout = self.new_stdout if self.patch_stderr: sys.stderr = self.new_stdout - def __exit__(self, *a, **kw): + def __exit__(self, *a, **kw): if self.patch_stdout: sys.stdout = self.original_stdout - + if self.patch_stderr: sys.stderr = self.original_stderr - - -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) + + +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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/__init__.py index baffc48825..a8b3a591b2 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/__init__.py @@ -1 +1 @@ -from __future__ import unicode_literals +from __future__ import unicode_literals diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/basic.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/basic.py index 401135dec0..3593df43e5 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/basic.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/basic.py @@ -1,123 +1,123 @@ -# pylint: disable=function-redefined -from __future__ import unicode_literals - -from prompt_toolkit.enums import DEFAULT_BUFFER +# pylint: disable=function-redefined +from __future__ import unicode_literals + +from prompt_toolkit.enums import DEFAULT_BUFFER from prompt_toolkit.filters import HasSelection, Condition, EmacsInsertMode, ViInsertMode -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.screen import Point +from prompt_toolkit.keys import Keys +from prompt_toolkit.layout.screen import Point from prompt_toolkit.mouse_events import MouseEventType, MouseEvent -from prompt_toolkit.renderer import HeightIsUnknownError -from prompt_toolkit.utils import suspend_to_background_supported, is_windows - +from prompt_toolkit.renderer import HeightIsUnknownError +from prompt_toolkit.utils import suspend_to_background_supported, is_windows + from .named_commands import get_by_name from ..registry import Registry - - -__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 - - + + +__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 = Registry() insert_mode = ViInsertMode() | EmacsInsertMode() handle = registry.add_binding - 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) + 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.ShiftUp) @handle(Keys.ShiftDown) @handle(Keys.ShiftRight) @handle(Keys.ShiftLeft) - @handle(Keys.Home) - @handle(Keys.End) - @handle(Keys.Delete) - @handle(Keys.ShiftDelete) + @handle(Keys.Home) + @handle(Keys.End) + @handle(Keys.Delete) + @handle(Keys.ShiftDelete) @handle(Keys.ControlDelete) - @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) + @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) @handle(Keys.Ignore) - def _(event): - """ - First, for any of these keys, Don't do anything by default. Also don't - catch them in the 'Any' handler which will insert them as data. - - If people want to insert these characters as a literal, they can always - do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi - mode.) - """ - pass - + def _(event): + """ + First, for any of these keys, Don't do anything by default. Also don't + catch them in the 'Any' handler which will insert them as data. + + If people want to insert these characters as a literal, they can always + do by doing a quoted insert. (ControlQ in emacs mode, ControlV in Vi + mode.) + """ + pass + # Readline-style bindings. handle(Keys.Home)(get_by_name('beginning-of-line')) handle(Keys.End)(get_by_name('end-of-line')) @@ -126,7 +126,7 @@ def load_basic_bindings(): handle(Keys.ControlUp)(get_by_name('previous-history')) handle(Keys.ControlDown)(get_by_name('next-history')) handle(Keys.ControlL)(get_by_name('clear-screen')) - + handle(Keys.ControlK, filter=insert_mode)(get_by_name('kill-line')) handle(Keys.ControlU, filter=insert_mode)(get_by_name('unix-line-discard')) handle(Keys.ControlH, filter=insert_mode, save_before=if_no_repeat)( @@ -143,76 +143,76 @@ def load_basic_bindings(): handle(Keys.ControlW, filter=insert_mode)(get_by_name('unix-word-rubout')) handle(Keys.ControlI, filter=insert_mode)(get_by_name('menu-complete')) handle(Keys.BackTab, filter=insert_mode)(get_by_name('menu-complete-backward')) - + handle(Keys.PageUp, filter= ~has_selection)(get_by_name('previous-history')) handle(Keys.PageDown, filter= ~has_selection)(get_by_name('next-history')) - # CTRL keys. - + # CTRL keys. + text_before_cursor = Condition(lambda cli: cli.current_buffer.text) handle(Keys.ControlD, filter=text_before_cursor & insert_mode)(get_by_name('delete-char')) - + is_multiline = Condition(lambda cli: cli.current_buffer.is_multiline()) is_returnable = Condition(lambda cli: cli.current_buffer.accept_action.is_returnable) - + @handle(Keys.ControlJ, filter=is_multiline & insert_mode) - def _(event): + def _(event): " Newline (in case of multiline input. " event.current_buffer.newline(copy_margin=not event.cli.in_paste_mode) - + @handle(Keys.ControlJ, filter=~is_multiline & is_returnable) - def _(event): + def _(event): " Enter, accept input. " buff = event.current_buffer buff.accept_action.validate_and_handle(event.cli, buff) - + # Delete the word before the cursor. - + @handle(Keys.Up) - def _(event): - event.current_buffer.auto_up(count=event.arg) - + def _(event): + event.current_buffer.auto_up(count=event.arg) + @handle(Keys.Down) - def _(event): - event.current_buffer.auto_down(count=event.arg) - - @handle(Keys.Delete, filter=has_selection) - def _(event): - data = event.current_buffer.cut_selection() - event.cli.clipboard.set_data(data) - + def _(event): + event.current_buffer.auto_down(count=event.arg) + + @handle(Keys.Delete, filter=has_selection) + def _(event): + data = event.current_buffer.cut_selection() + event.cli.clipboard.set_data(data) + # Global bindings. - - @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) - + + @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) + @handle(Keys.CPRResponse, save_before=lambda e: False) - def _(event): - """ - Handle incoming Cursor-Position-Request response. - """ - # The incoming data looks like u'\x1b[35;1R' - # Parse row/col information. - row, col = map(int, event.data[2:-1].split(';')) - - # Report absolute cursor position to the renderer. - event.cli.renderer.report_absolute_cursor_row(row) - + 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) + @handle(Keys.BracketedPaste) - def _(event): - " Pasting from clipboard. " + def _(event): + " Pasting from clipboard. " data = event.data - + # Be sure to use \n as line ending. # Some terminals (Like iTerm2) seem to paste \r\n line endings in a # bracketed paste. See: https://github.com/ipython/ipython/issues/9737 @@ -239,169 +239,169 @@ def load_mouse_bindings(): """ registry = Registry() - @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 = { + @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: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, - }.get(mouse_event) + }.get(mouse_event) # Handle situations where `PosixStdinReader` used surrogateescapes. if x >= 0xdc00: x-= 0xdc00 if y >= 0xdc00: y-= 0xdc00 - x -= 32 - y -= 32 - else: - # Urxvt and Xterm SGR. - # When the '<' is not present, we are not using the Xterm SGR mode, - # but Urxvt instead. - data = event.data[2:] - if data[:1] == '<': - sgr = True - data = data[1:] - else: - sgr = False - - # Extract coordinates. - mouse_event, x, y = map(int, data[:-1].split(';')) - m = data[-1] - - # Parse event type. - if sgr: - mouse_event = { + x -= 32 + y -= 32 + else: + # Urxvt and Xterm SGR. + # When the '<' is not present, we are not using the Xterm SGR mode, + # but Urxvt instead. + data = event.data[2:] + if data[:1] == '<': + sgr = True + data = data[1:] + else: + sgr = False + + # Extract coordinates. + mouse_event, x, y = map(int, data[:-1].split(';')) + m = data[-1] + + # Parse event type. + if sgr: + mouse_event = { (0, 'M'): MouseEventType.MOUSE_DOWN, (0, 'm'): MouseEventType.MOUSE_UP, (64, 'M'): MouseEventType.SCROLL_UP, (65, 'M'): MouseEventType.SCROLL_DOWN, - }.get((mouse_event, m)) - else: - mouse_event = { + }.get((mouse_event, m)) + else: + mouse_event = { 32: MouseEventType.MOUSE_DOWN, 35: MouseEventType.MOUSE_UP, 96: MouseEventType.SCROLL_UP, 97: MouseEventType.SCROLL_DOWN, - }.get(mouse_event) - - x -= 1 - y -= 1 - - # Only handle mouse events when we know the window height. - if event.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)) - + }.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)) + return registry - + def load_abort_and_exit_bindings(): - """ - Basic bindings for abort (Ctrl-C) and exit (Ctrl-D). - """ + """ + Basic bindings for abort (Ctrl-C) and exit (Ctrl-D). + """ registry = Registry() handle = registry.add_binding - - @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.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)(get_by_name('end-of-file')) - + return registry - + def load_basic_system_bindings(): - """ - Basic system bindings (For both Emacs and Vi mode.) - """ + """ + Basic system bindings (For both Emacs and Vi mode.) + """ registry = Registry() - - suspend_supported = Condition( - lambda cli: suspend_to_background_supported()) - + + suspend_supported = Condition( + lambda cli: suspend_to_background_supported()) + @registry.add_binding(Keys.ControlZ, filter=suspend_supported) - def _(event): - """ - Suspend process to background. - """ - event.cli.suspend_to_background() - + def _(event): + """ + Suspend process to background. + """ + event.cli.suspend_to_background() + return registry - + def load_auto_suggestion_bindings(): - """ - Key bindings for accepting auto suggestion text. - """ + """ + Key bindings for accepting auto suggestion text. + """ registry = Registry() handle = registry.add_binding - - 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) + + 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) return registry diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/emacs.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/emacs.py index bccdb04ff3..5a0c502a57 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/emacs.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/emacs.py @@ -1,47 +1,47 @@ -# 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 +# 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 Condition, EmacsMode, HasSelection, EmacsInsertMode, HasFocus, HasArg from prompt_toolkit.completion import CompleteEvent - -from .scroll import scroll_page_up, scroll_page_down + +from .scroll import scroll_page_up, scroll_page_down from .named_commands import get_by_name from ..registry import Registry, ConditionalRegistry - -__all__ = ( - 'load_emacs_bindings', - 'load_emacs_search_bindings', - 'load_emacs_system_bindings', - 'load_extra_emacs_page_navigation_bindings', -) - - + +__all__ = ( + 'load_emacs_bindings', + 'load_emacs_search_bindings', + 'load_emacs_system_bindings', + 'load_extra_emacs_page_navigation_bindings', +) + + def load_emacs_bindings(): - """ - Some e-macs extensions. - """ - # Overview of Readline emacs commands: - # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf + """ + Some e-macs extensions. + """ + # Overview of Readline emacs commands: + # http://www.catonmat.net/download/readline-emacs-editing-mode-cheat-sheet.pdf registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding - + insert_mode = EmacsInsertMode() has_selection = 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.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)(get_by_name('beginning-of-line')) handle(Keys.ControlB)(get_by_name('backward-char')) handle(Keys.ControlDelete, filter=insert_mode)(get_by_name('kill-word')) @@ -61,14 +61,14 @@ def load_emacs_bindings(): handle(Keys.Escape, Keys.ControlH, filter=insert_mode)(get_by_name('backward-kill-word')) handle(Keys.Escape, Keys.Backspace, filter=insert_mode)(get_by_name('backward-kill-word')) handle(Keys.Escape, '\\', filter=insert_mode)(get_by_name('delete-horizontal-space')) - + handle(Keys.ControlUnderscore, save_before=(lambda e: False), filter=insert_mode)( get_by_name('undo')) - + handle(Keys.ControlX, Keys.ControlU, save_before=(lambda e: False), filter=insert_mode)( get_by_name('undo')) - - + + handle(Keys.Escape, '<', filter= ~has_selection)(get_by_name('beginning-of-history')) handle(Keys.Escape, '>', filter= ~has_selection)(get_by_name('end-of-history')) @@ -88,365 +88,365 @@ def load_emacs_bindings(): handle(Keys.ControlX, 'e')(get_by_name('call-last-kbd-macro')) @handle(Keys.ControlN) - def _(event): + def _(event): " Next line. " - event.current_buffer.auto_down() - + event.current_buffer.auto_down() + @handle(Keys.ControlP) - def _(event): + def _(event): " Previous line. " - event.current_buffer.auto_up(count=event.arg) - - def handle_digit(c): - """ + event.current_buffer.auto_up(count=event.arg) + + def handle_digit(c): + """ Handle input of arguments. The first number needs to be preceeded by escape. - """ + """ @handle(c, filter=HasArg()) - @handle(Keys.Escape, c) - def _(event): - event.append_to_arg_count(c) - - for c in '0123456789': - handle_digit(c) - + @handle(Keys.Escape, c) + def _(event): + event.append_to_arg_count(c) + + for c in '0123456789': + handle_digit(c) + @handle(Keys.Escape, '-', filter=~HasArg()) - def _(event): - """ - """ - if event._arg is None: - event.append_to_arg_count('-') - + def _(event): + """ + """ + if event._arg is None: + event.append_to_arg_count('-') + @handle('-', filter=Condition(lambda cli: cli.input_processor.arg == '-')) - def _(event): - """ + def _(event): + """ When '-' is typed again, after exactly '-' has been given as an argument, ignore this. - """ + """ event.cli.input_processor.arg = '-' - + is_returnable = Condition( lambda cli: cli.current_buffer.accept_action.is_returnable) - + # Meta + Newline: always accept input. handle(Keys.Escape, Keys.ControlJ, filter=insert_mode & is_returnable)( get_by_name('accept-line')) - + def character_search(buff, char, count): if count < 0: match = buff.document.find_backwards(char, in_current_line=True, count=-count) else: match = buff.document.find(char, in_current_line=True, count=count) - + if match is not None: buff.cursor_position += match - + @handle(Keys.ControlSquareClose, Keys.Any) - def _(event): + def _(event): " When Ctl-] + a character is pressed. go to that character. " # Also named 'character-search' character_search(event.current_buffer, event.data, event.arg) - + @handle(Keys.Escape, Keys.ControlSquareClose, Keys.Any) - def _(event): + def _(event): " Like Ctl-], but backwards. " # Also named 'character-search-backward' character_search(event.current_buffer, event.data, -event.arg) - + @handle(Keys.Escape, 'a') - def _(event): + def _(event): " Previous sentence. " # TODO: - + @handle(Keys.Escape, 'e') - def _(event): + def _(event): " Move to end of sentence. " - # TODO: - + # TODO: + @handle(Keys.Escape, 't', filter=insert_mode) - def _(event): - """ - Swap the last two words before the cursor. - """ - # TODO - + def _(event): + """ + Swap the last two words before the cursor. + """ + # TODO + @handle(Keys.Escape, '*', filter=insert_mode) - def _(event): - """ + def _(event): + """ `meta-*`: Insert all possible completions of the preceding text. - """ + """ buff = event.current_buffer - + # List all completions. complete_event = CompleteEvent(text_inserted=False, completion_requested=True) completions = list(buff.completer.get_completions(buff.document, complete_event)) - + # Insert them. text_to_insert = ' '.join(c.text for c in completions) buff.insert_text(text_to_insert) - - @handle(Keys.ControlX, Keys.ControlX) - def _(event): - """ - Move cursor back and forth between the start and end of the current - line. - """ - buffer = event.current_buffer - + + @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.is_cursor_at_the_end_of_line: - buffer.cursor_position += buffer.document.get_start_of_line_position(after_whitespace=False) - else: - buffer.cursor_position += buffer.document.get_end_of_line_position() - - @handle(Keys.ControlSpace) - def _(event): - """ + 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 (if the current buffer is not empty). - """ - # Take the current cursor position as the start of this selection. + """ + # Take the current cursor position as the start of this selection. buff = event.current_buffer if buff.text: buff.start_selection(selection_type=SelectionType.CHARACTERS) - - @handle(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, 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.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, 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=insert_mode) - 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 _(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) + return registry - + def load_emacs_open_in_editor_bindings(): - """ - Pressing C-X C-E will open the buffer in an external editor. - """ + """ + Pressing C-X C-E will open the buffer in an external editor. + """ registry = Registry() - + registry.add_binding(Keys.ControlX, Keys.ControlE, filter=EmacsMode() & ~HasSelection())( get_by_name('edit-and-execute-command')) - + return registry - + def load_emacs_system_bindings(): registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding has_focus = 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() - + + @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() + return registry - - + + def load_emacs_search_bindings(get_search_state=None): registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding has_focus = 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) + 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) @handle(Keys.Escape, filter=has_focus, eager=True) - 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) - + 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) + def incremental_search(cli, direction, count=1): " Apply search, but keep search buffer focussed. " - # Update search_state. + # Update search_state. search_state = get_search_state(cli) direction_changed = search_state.direction != direction - + search_state.text = cli.buffers[SEARCH_BUFFER].text search_state.direction = direction - - # Apply search to current buffer. - if not direction_changed: + + # Apply search to current buffer. + if not direction_changed: input_buffer = cli.buffers.previous(cli) - input_buffer.apply_search(search_state, + input_buffer.apply_search(search_state, include_current_position=False, count=count) - + @handle(Keys.ControlR, filter=has_focus) @handle(Keys.Up, filter=has_focus) def _(event): incremental_search(event.cli, IncrementalSearchDirection.BACKWARD, count=event.arg) - @handle(Keys.ControlS, filter=has_focus) - @handle(Keys.Down, filter=has_focus) - def _(event): + @handle(Keys.ControlS, filter=has_focus) + @handle(Keys.Down, filter=has_focus) + def _(event): incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg) - + return registry - + def load_extra_emacs_page_navigation_bindings(): - """ - Key bindings, for scrolling up and down through pages. - This are separate bindings, because GNU readline doesn't have them. - """ + """ + Key bindings, for scrolling up and down through pages. + This are separate bindings, because GNU readline doesn't have them. + """ registry = ConditionalRegistry(Registry(), EmacsMode()) handle = registry.add_binding - - 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) + + 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) return registry diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/scroll.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/scroll.py index 2cc58129ff..169517942c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/scroll.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/scroll.py @@ -1,139 +1,139 @@ -""" -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 +""" +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 from six.moves import range - -__all__ = ( - 'scroll_forward', - 'scroll_backward', - 'scroll_half_page_up', - 'scroll_half_page_down', - 'scroll_one_line_up', - 'scroll_one_line_down', -) - - -def _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: + +__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: info = w.render_info ui_content = info.ui_content # Height to scroll. scroll_height = info.window_height - if half: + if half: scroll_height //= 2 - + # Calculate how many lines is equivalent to that vertical space. y = b.document.cursor_position_row + 1 height = 0 while y < ui_content.line_count: line_height = info.get_height_for_line(y) - + if height + line_height < scroll_height: height += line_height y += 1 else: break - + b.cursor_position = b.document.translate_row_col_to_index(y, 0) -def scroll_backward(event, half=False): - """ - Scroll window up. - """ - w = _current_window_for_event(event) - b = event.cli.current_buffer - - if w and w.render_info: +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: info = w.render_info # Height to scroll. scroll_height = info.window_height - if half: + if half: scroll_height //= 2 - + # Calculate how many lines is equivalent to that vertical space. y = max(0, b.document.cursor_position_row - 1) height = 0 while y > 0: line_height = info.get_height_for_line(y) - + if height + line_height < scroll_height: height += line_height y -= 1 else: break - + b.cursor_position = b.document.translate_row_col_to_index(y, 0) -def scroll_half_page_down(event): - """ - Same as ControlF, but only scroll half a page. - """ - scroll_forward(event, half=True) - - -def scroll_half_page_up(event): - """ - Same as ControlB, but only scroll half a page. - """ - scroll_backward(event, half=True) - - -def scroll_one_line_down(event): - """ - scroll_offset += 1 - """ - w = 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: +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: first_line_height = info.get_height_for_line(info.first_visible_line()) cursor_up = info.cursor_position.y - (info.window_height - 1 - first_line_height - @@ -142,44 +142,44 @@ def scroll_one_line_up(event): # Move cursor up, as many steps as the height of the first line. # TODO: not entirely correct yet, in case of line wrapping and many long lines. for _ in range(max(0, cursor_up)): - b.cursor_position += b.document.get_cursor_up_position() - - # Scroll window - w.vertical_scroll -= 1 - - -def scroll_page_down(event): - """ - Scroll page down. (Prefer the cursor at the top of the page, after scrolling.) - """ - w = _current_window_for_event(event) - b = event.cli.current_buffer - - if w and w.render_info: - # Scroll down one page. + 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. line_index = max(w.render_info.last_visible_line(), w.vertical_scroll + 1) w.vertical_scroll = line_index - + b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) - b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) - - -def scroll_page_up(event): - """ - Scroll page up. (Prefer the cursor at the bottom of the page, after scrolling.) - """ - w = _current_window_for_event(event) - b = event.cli.current_buffer - - if w and w.render_info: + 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: # Put cursor at the first visible line. (But make sure that the cursor # moves at least one line up.) line_index = max(0, min(w.render_info.first_visible_line(), b.document.cursor_position_row - 1)) - + b.cursor_position = b.document.translate_row_col_to_index(line_index, 0) b.cursor_position += b.document.get_start_of_line_position(after_whitespace=True) - + # Set the scroll offset. We can safely set it to zero; the Window will # make sure that it scrolls at least until the cursor becomes visible. w.vertical_scroll = 0 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/utils.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/utils.py index caf08c5c1b..226ae8a5c5 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/utils.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/utils.py @@ -1,25 +1,25 @@ -from __future__ import unicode_literals -from prompt_toolkit.filters import CLIFilter, Always - -__all__ = ( - 'create_handle_decorator', -) - -def create_handle_decorator(registry, filter=Always()): - """ +from __future__ import unicode_literals +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 will chain the given filter to every key binding. - + :param filter: `CLIFilter` - """ - assert isinstance(filter, CLIFilter) - - def handle(*keys, **kw): - # Chain the given filter to the filter of this specific binding. - if 'filter' in kw: - kw['filter'] = kw['filter'] & filter - else: - kw['filter'] = filter - + """ + assert isinstance(filter, CLIFilter) + + def handle(*keys, **kw): + # Chain the given filter to the filter of this specific binding. + if 'filter' in kw: + kw['filter'] = kw['filter'] & filter + else: + kw['filter'] = filter + return registry.add_binding(*keys, **kw) - return handle + return handle diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/vi.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/vi.py index 72568ee273..a436edf3a7 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/vi.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/bindings/vi.py @@ -1,26 +1,26 @@ -# pylint: disable=function-redefined -from __future__ import unicode_literals +# pylint: disable=function-redefined +from __future__ import unicode_literals from prompt_toolkit.buffer import ClipboardData, indent, unindent, reshape_text -from prompt_toolkit.document import Document -from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER, SYSTEM_BUFFER +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, IsReadOnly from prompt_toolkit.filters.cli import ViNavigationMode, ViInsertMode, ViInsertMultipleMode, ViReplaceMode, ViSelectionMode, ViWaitingForTextObjectMode, ViDigraphMode, ViMode from prompt_toolkit.key_binding.digraphs import DIGRAPHS -from prompt_toolkit.key_binding.vi_state import CharacterFind, InputMode -from prompt_toolkit.keys import Keys -from prompt_toolkit.layout.utils import find_window_for_buffer_name +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, SelectionState, PasteMode - -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 + +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 from .named_commands import get_by_name from ..registry import Registry, ConditionalRegistry, BaseRegistry - -import prompt_toolkit.filters as filters + +import prompt_toolkit.filters as filters from six.moves import range -import codecs +import codecs import six import string - + try: from itertools import accumulate except ImportError: # < Python 3.2 @@ -31,38 +31,38 @@ except ImportError: # < Python 3.2 total += item yield total -__all__ = ( - 'load_vi_bindings', - 'load_vi_search_bindings', - 'load_vi_system_bindings', - 'load_extra_vi_page_navigation_bindings', -) - +__all__ = ( + 'load_vi_bindings', + 'load_vi_search_bindings', + 'load_vi_system_bindings', + 'load_extra_vi_page_navigation_bindings', +) + if six.PY2: ascii_lowercase = string.ascii_lowercase.decode('ascii') else: ascii_lowercase = string.ascii_lowercase - + vi_register_names = ascii_lowercase + '0123456789' - - + + class TextObjectType(object): EXCLUSIVE = 'EXCLUSIVE' INCLUSIVE = 'INCLUSIVE' LINEWISE = 'LINEWISE' BLOCK = 'BLOCK' - - + + class TextObject(object): - """ + """ Return struct for functions wrapped in ``text_object``. - Both `start` and `end` are relative to the current cursor position. - """ + Both `start` and `end` are relative to the current cursor position. + """ def __init__(self, start, end=0, type=TextObjectType.EXCLUSIVE): - self.start = start - self.end = end + self.start = start + self.end = end self.type = type - + @property def selection_type(self): if self.type == TextObjectType.LINEWISE: @@ -72,15 +72,15 @@ class TextObject(object): else: return SelectionType.CHARACTERS - def sorted(self): - """ - Return a (start, end) tuple where start <= end. - """ - if self.start < self.end: - return self.start, self.end - else: - return self.end, self.start - + def sorted(self): + """ + Return a (start, end) tuple where start <= end. + """ + if self.start < self.end: + return self.start, self.end + else: + return self.end, self.start + def operator_range(self, document): """ Return a (start, end) tuple with start <= end that indicates the range @@ -89,7 +89,7 @@ class TextObject(object): """ start, end = self.sorted() doc = document - + if (self.type == TextObjectType.EXCLUSIVE and doc.translate_index_to_position(end + doc.cursor_position)[1] == 0): # If the motion is exclusive and the end of motion is on the first @@ -138,7 +138,7 @@ class TextObject(object): def create_text_object_decorator(registry): - """ + """ Create a decorator that can be used to register Vi text object implementations. """ assert isinstance(registry, BaseRegistry) @@ -299,30 +299,30 @@ def create_operator_decorator(registry): def load_vi_bindings(get_search_state=None): """ - Vi extensions. - - # Overview of Readline Vi commands: - # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf - + Vi extensions. + + # Overview of Readline Vi commands: + # http://www.catonmat.net/download/bash-vi-editing-mode-cheat-sheet.pdf + :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. - + """ + # 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. + registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding - - # Default get_search_state. - if get_search_state is None: - def get_search_state(cli): return cli.search_state - + + # Default get_search_state. + if get_search_state is None: + def get_search_state(cli): return cli.search_state + # (Note: Always take the navigation bindings in read-only mode, even when # ViState says different.) navigation_mode = ViNavigationMode() @@ -332,63 +332,63 @@ def load_vi_bindings(get_search_state=None): selection_mode = ViSelectionMode() operator_given = ViWaitingForTextObjectMode() digraph_mode = ViDigraphMode() - - vi_transform_functions = [ - # Rot 13 transformation + + vi_transform_functions = [ + # Rot 13 transformation (('g', '?'), Always(), lambda string: codecs.encode(string, 'rot_13')), - - # To lowercase + + # To lowercase (('g', 'u'), Always(), lambda string: string.lower()), - - # To uppercase. + + # To uppercase. (('g', 'U'), Always(), lambda string: string.upper()), - - # Swap case. + + # Swap case. (('g', '~'), Always(), lambda string: string.swapcase()), (('~', ), Condition(lambda cli: cli.vi_state.tilde_operator), lambda string: string.swapcase()), - ] - + ] + # Insert a character literally (quoted insert). handle(Keys.ControlV, filter=insert_mode)(get_by_name('quoted-insert')) - @handle(Keys.Escape) - def _(event): - """ - Escape goes to vi navigation mode. - """ - buffer = event.current_buffer + @handle(Keys.Escape) + def _(event): + """ + Escape goes to vi navigation mode. + """ + buffer = event.current_buffer vi_state = event.cli.vi_state - - if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): - buffer.cursor_position += buffer.document.get_cursor_left_position() - + + if vi_state.input_mode in (InputMode.INSERT, InputMode.REPLACE): + buffer.cursor_position += buffer.document.get_cursor_left_position() + vi_state.reset(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(Keys.Up, filter=navigation_mode) - @handle(Keys.ControlP, filter=navigation_mode) - def _(event): - """ - Arrow up and ControlP in navigation mode go up. - """ + + 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(Keys.Up, filter=navigation_mode) + @handle(Keys.ControlP, filter=navigation_mode) + def _(event): + """ + Arrow up and ControlP in navigation mode go up. + """ event.current_buffer.auto_up(count=event.arg) - + @handle('k', filter=navigation_mode) def _(event): """ @@ -398,14 +398,14 @@ def load_vi_bindings(get_search_state=None): event.current_buffer.auto_up( count=event.arg, go_to_start_of_line_if_history_changes=True) - @handle(Keys.Down, filter=navigation_mode) - @handle(Keys.ControlN, filter=navigation_mode) - def _(event): - """ - Arrow down and Control-N in navigation mode. - """ + @handle(Keys.Down, filter=navigation_mode) + @handle(Keys.ControlN, filter=navigation_mode) + def _(event): + """ + Arrow down and Control-N in navigation mode. + """ event.current_buffer.auto_down(count=event.arg) - + @handle('j', filter=navigation_mode) def _(event): """ @@ -415,143 +415,143 @@ def load_vi_bindings(get_search_state=None): count=event.arg, go_to_start_of_line_if_history_changes=True) @handle(Keys.ControlH, filter=navigation_mode) - @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.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.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.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) # XXX: only if the selected buffer has a return handler. - 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. " + 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. " event.cli.vi_state.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() + + @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() event.cli.vi_state.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() + + @handle('A', filter=navigation_mode & ~IsReadOnly()) + def _(event): + event.current_buffer.cursor_position += event.current_buffer.document.get_end_of_line_position() event.cli.vi_state.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) + + @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) event.cli.vi_state.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()) + + @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()) event.cli.vi_state.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]) + + @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)) - + 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('x', filter=selection_mode) def _(event): """ @@ -561,16 +561,16 @@ def load_vi_bindings(get_search_state=None): clipboard_data = event.current_buffer.cut_selection() event.cli.clipboard.set_data(clipboard_data) - @handle('i', filter=navigation_mode & ~IsReadOnly()) - def _(event): + @handle('i', filter=navigation_mode & ~IsReadOnly()) + def _(event): event.cli.vi_state.input_mode = InputMode.INSERT - - @handle('I', filter=navigation_mode & ~IsReadOnly()) - def _(event): + + @handle('I', filter=navigation_mode & ~IsReadOnly()) + def _(event): event.cli.vi_state.input_mode = InputMode.INSERT event.current_buffer.cursor_position += \ event.current_buffer.document.get_start_of_line_position(after_whitespace=True) - + @Condition def in_block_selection(cli): buff = cli.current_buffer @@ -603,17 +603,17 @@ def load_vi_bindings(get_search_state=None): buff.exit_selection() @handle('A', filter=in_block_selection & ~IsReadOnly()) - def _(event): + def _(event): go_to_block_selection(event, after=True) @handle('J', filter=navigation_mode & ~IsReadOnly()) def _(event): " Join lines. " - for i in range(event.arg): - event.current_buffer.join_next_line() - + for i in range(event.arg): + event.current_buffer.join_next_line() + @handle('g', 'J', filter=navigation_mode & ~IsReadOnly()) - def _(event): + def _(event): " Join lines without space. " for i in range(event.arg): event.current_buffer.join_next_line(separator='') @@ -621,33 +621,33 @@ def load_vi_bindings(get_search_state=None): @handle('J', filter=selection_mode & ~IsReadOnly()) def _(event): " Join selected lines. " - event.current_buffer.join_selected_lines() - + event.current_buffer.join_selected_lines() + @handle('g', 'J', filter=selection_mode & ~IsReadOnly()) def _(event): " Join selected lines without space. " event.current_buffer.join_selected_lines(separator='') - - @handle('p', filter=navigation_mode) - def _(event): - """ - Paste after - """ - event.current_buffer.paste_clipboard_data( - event.cli.clipboard.get_data(), + + @handle('p', filter=navigation_mode) + def _(event): + """ + Paste after + """ + event.current_buffer.paste_clipboard_data( + event.cli.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.VI_AFTER) - - @handle('P', filter=navigation_mode) - def _(event): - """ - Paste before - """ - event.current_buffer.paste_clipboard_data( - event.cli.clipboard.get_data(), + + @handle('P', filter=navigation_mode) + def _(event): + """ + Paste before + """ + event.current_buffer.paste_clipboard_data( + event.cli.clipboard.get_data(), count=event.arg, paste_mode=PasteMode.VI_BEFORE) - + @handle('"', Keys.Any, 'p', filter=navigation_mode) def _(event): " Paste from named register. " @@ -668,190 +668,190 @@ def load_vi_bindings(get_search_state=None): event.current_buffer.paste_clipboard_data( data, count=event.arg, paste_mode=PasteMode.VI_BEFORE) - @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. - """ + @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. + """ event.cli.vi_state.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) + + @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) event.cli.vi_state.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('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) - 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=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('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) + 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=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('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) event.cli.vi_state.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) + + @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) event.cli.vi_state.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': + + @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': buffer.insert_text(c.swapcase(), overwrite=True) - + @handle('g', 'u', 'u', filter=navigation_mode & ~IsReadOnly()) def _(event): " Lowercase current line. " @@ -870,64 +870,64 @@ def load_vi_bindings(get_search_state=None): buff = event.current_buffer buff.transform_current_line(lambda s: s.swapcase()) - @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. + @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. # XXX: should become text_object. - pass - - @handle(')', filter=navigation_mode) - def _(event): - # TODO: go to end of sentence. + pass + + @handle(')', filter=navigation_mode) + def _(event): + # TODO: go to end of sentence. # XXX: should become text_object. - pass - + pass + operator = create_operator_decorator(registry) text_object = create_text_object_decorator(registry) - + @text_object(Keys.Any, filter=operator_given) def _(event): """ Unknown key binding while waiting for a text object. """ event.cli.output.bell() - + # # *** Operators *** # - + def create_delete_and_change_operators(delete_only, with_register=False): """ Delete and change operators. - + :param delete_only: Create an operator that deletes, but doesn't go to insert mode. :param with_register: Copy the deleted text to this named register instead of the clipboard. """ @@ -935,16 +935,16 @@ def load_vi_bindings(get_search_state=None): handler_keys = ('"', Keys.Any, 'cd'[delete_only]) else: handler_keys = 'cd'[delete_only] - + @operator(*handler_keys, filter=~IsReadOnly()) def delete_or_change_operator(event, text_object): clipboard_data = None buff = event.current_buffer - + if text_object: new_document, clipboard_data = text_object.cut(buff) buff.document = new_document - + # Set deleted/changed text to clipboard or named register. if clipboard_data and clipboard_data.text: if with_register: @@ -953,11 +953,11 @@ def load_vi_bindings(get_search_state=None): event.cli.vi_state.named_registers[reg_name] = clipboard_data else: event.cli.clipboard.set_data(clipboard_data) - + # Only go back to insert mode in case of 'change'. if not delete_only: event.cli.vi_state.input_mode = InputMode.INSERT - + create_delete_and_change_operators(False, False) create_delete_and_change_operators(False, True) create_delete_and_change_operators(True, False) @@ -1034,68 +1034,68 @@ def load_vi_bindings(get_search_state=None): # @text_object('b') - def _(event): - """ Move one word or token left. """ + def _(event): + """ Move one word or token left. """ return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg) or 0) - + @text_object('B') - def _(event): - """ Move one non-blank word left """ + def _(event): + """ Move one non-blank word left """ return TextObject(event.current_buffer.document.find_start_of_previous_word(count=event.arg, WORD=True) or 0) - + @text_object('$') - def key_dollar(event): - """ 'c$', 'd$' and '$': Delete/change/move until end of line. """ + def key_dollar(event): + """ 'c$', 'd$' and '$': Delete/change/move until end of line. """ return TextObject(event.current_buffer.document.get_end_of_line_position()) - + @text_object('w') - def _(event): - """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. """ + def _(event): + """ 'word' forward. 'cw', 'dw', 'w': Delete/change/move one word. """ return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg) or - event.current_buffer.document.get_end_of_document_position()) - + event.current_buffer.document.get_end_of_document_position()) + @text_object('W') - def _(event): - """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. """ + def _(event): + """ 'WORD' forward. 'cW', 'dW', 'W': Delete/change/move one WORD. """ return TextObject(event.current_buffer.document.find_next_word_beginning(count=event.arg, WORD=True) or - event.current_buffer.document.get_end_of_document_position()) - + event.current_buffer.document.get_end_of_document_position()) + @text_object('e') - def _(event): - """ End of 'word': 'ce', 'de', 'e' """ - end = event.current_buffer.document.find_next_word_ending(count=event.arg) + def _(event): + """ End of 'word': 'ce', 'de', 'e' """ + end = event.current_buffer.document.find_next_word_ending(count=event.arg) return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) - + @text_object('E') - def _(event): - """ End of 'WORD': 'cE', 'dE', 'E' """ - end = event.current_buffer.document.find_next_word_ending(count=event.arg, WORD=True) + def _(event): + """ End of 'WORD': 'cE', 'dE', 'E' """ + end = event.current_buffer.document.find_next_word_ending(count=event.arg, WORD=True) return TextObject(end - 1 if end else 0, type=TextObjectType.INCLUSIVE) - + @text_object('i', 'w', no_move_handler=True) - def _(event): - """ Inner 'word': ciw and diw """ - start, end = event.current_buffer.document.find_boundaries_of_current_word() + def _(event): + """ Inner 'word': ciw and diw """ + start, end = event.current_buffer.document.find_boundaries_of_current_word() return TextObject(start, end) - + @text_object('a', 'w', no_move_handler=True) - def _(event): - """ A 'word': caw and daw """ - start, end = event.current_buffer.document.find_boundaries_of_current_word(include_trailing_whitespace=True) + def _(event): + """ A 'word': caw and daw """ + start, end = event.current_buffer.document.find_boundaries_of_current_word(include_trailing_whitespace=True) return TextObject(start, end) - + @text_object('i', 'W', no_move_handler=True) - def _(event): - """ Inner 'WORD': ciW and diW """ - start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True) + def _(event): + """ Inner 'WORD': ciW and diW """ + start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True) return TextObject(start, end) - + @text_object('a', 'W', no_move_handler=True) - def _(event): - """ A 'WORD': caw and daw """ - start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True, include_trailing_whitespace=True) + def _(event): + """ A 'WORD': caw and daw """ + start, end = event.current_buffer.document.find_boundaries_of_current_word(WORD=True, include_trailing_whitespace=True) return TextObject(start, end) - + @text_object('a', 'p', no_move_handler=True) def _(event): """ @@ -1106,24 +1106,24 @@ def load_vi_bindings(get_search_state=None): return TextObject(start, end) @text_object('^') - def key_circumflex(event): - """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """ + def key_circumflex(event): + """ 'c^', 'd^' and '^': Soft start of line, after whitespace. """ return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=True)) - + @text_object('0') - def key_zero(event): - """ - 'c0', 'd0': Hard start of line, before whitespace. - (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) - """ + def key_zero(event): + """ + 'c0', 'd0': Hard start of line, before whitespace. + (The move '0' key is implemented elsewhere, because a '0' could also change the `arg`.) + """ return TextObject(event.current_buffer.document.get_start_of_line_position(after_whitespace=False)) - + def create_ci_ca_handles(ci_start, ci_end, inner, key=None): - # TODO: 'dat', 'dit', (tags (like xml) - """ - Delete/Change string between this start and stop character. But keep these characters. - This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. - """ + # TODO: 'dat', 'dit', (tags (like xml) + """ + Delete/Change string between this start and stop character. But keep these characters. + This implements all the ci", ci<, ci{, ci(, di", di<, ca", ca<, ... combinations. + """ def handler(event): if ci_start == ci_end: # Quotes @@ -1133,53 +1133,53 @@ def load_vi_bindings(get_search_state=None): # Brackets start = event.current_buffer.document.find_enclosing_bracket_left(ci_start, ci_end) end = event.current_buffer.document.find_enclosing_bracket_right(ci_start, ci_end) - - if start is not None and end is not None: - offset = 0 if inner else 1 + + if start is not None and end is not None: + offset = 0 if inner else 1 return TextObject(start + 1 - offset, end + offset) - else: - # Nothing found. + else: + # Nothing found. return TextObject(0) - + if key is None: text_object('ai'[inner], ci_start, no_move_handler=True)(handler) text_object('ai'[inner], ci_end, no_move_handler=True)(handler) else: text_object('ai'[inner], key, no_move_handler=True)(handler) - for inner in (False, True): - for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"), - ('[', ']'), ('<', '>'), ('{', '}'), ('(', ')')]: - create_ci_ca_handles(ci_start, ci_end, inner) - + for inner in (False, True): + for ci_start, ci_end in [('"', '"'), ("'", "'"), ("`", "`"), + ('[', ']'), ('<', '>'), ('{', '}'), ('(', ')')]: + create_ci_ca_handles(ci_start, ci_end, inner) + create_ci_ca_handles('(', ')', inner, 'b') # 'dab', 'dib' create_ci_ca_handles('{', '}', inner, 'B') # 'daB', 'diB' @text_object('{') - def _(event): - """ - Move to previous blank-line separated section. - Implements '{', 'c{', 'd{', 'y{' - """ + def _(event): + """ + Move to previous blank-line separated section. + Implements '{', 'c{', 'd{', 'y{' + """ index = event.current_buffer.document.start_of_paragraph( count=event.arg, before=True) return TextObject(index) - + @text_object('}') - def _(event): - """ - Move to next blank-line separated section. - Implements '}', 'c}', 'd}', 'y}' - """ + def _(event): + """ + Move to next blank-line separated section. + Implements '}', 'c}', 'd}', 'y}' + """ index = event.current_buffer.document.end_of_paragraph(count=event.arg, after=True) return TextObject(index) - + @text_object('f', Keys.Any) - def _(event): - """ - Go to next occurance of character. Typing 'fx' will move the - cursor to the next occurance of character. 'x'. - """ + def _(event): + """ + Go to next occurance of character. Typing 'fx' will move the + cursor to the next occurance of character. 'x'. + """ event.cli.vi_state.last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find( event.data, in_current_line=True, count=event.arg) @@ -1187,22 +1187,22 @@ def load_vi_bindings(get_search_state=None): return TextObject(match, type=TextObjectType.INCLUSIVE) else: return TextObject(0) - + @text_object('F', Keys.Any) - def _(event): - """ - Go to previous occurance of character. Typing 'Fx' will move the - cursor to the previous occurance of character. 'x'. - """ + def _(event): + """ + Go to previous occurance of character. Typing 'Fx' will move the + cursor to the previous occurance of character. 'x'. + """ event.cli.vi_state.last_character_find = CharacterFind(event.data, True) return TextObject(event.current_buffer.document.find_backwards( event.data, in_current_line=True, count=event.arg) or 0) - + @text_object('t', Keys.Any) - def _(event): - """ - Move right to the next occurance of c, then one char backward. - """ + def _(event): + """ + Move right to the next occurance of c, then one char backward. + """ event.cli.vi_state.last_character_find = CharacterFind(event.data, False) match = event.current_buffer.document.find( event.data, in_current_line=True, count=event.arg) @@ -1210,138 +1210,138 @@ def load_vi_bindings(get_search_state=None): return TextObject(match - 1, type=TextObjectType.INCLUSIVE) else: return TextObject(0) - + @text_object('T', Keys.Any) - def _(event): - """ - Move left to the previous occurance of c, then one char forward. - """ + def _(event): + """ + Move left to the previous occurance of c, then one char forward. + """ event.cli.vi_state.last_character_find = CharacterFind(event.data, True) match = event.current_buffer.document.find_backwards( event.data, in_current_line=True, count=event.arg) return TextObject(match + 1 if match else 0) - - def repeat(reverse): - """ - Create ',' and ';' commands. - """ + + def repeat(reverse): + """ + Create ',' and ';' commands. + """ @text_object(',' if reverse else ';') - def _(event): - # Repeat the last 'f'/'F'/'t'/'T' command. - pos = 0 + def _(event): + # Repeat the last 'f'/'F'/'t'/'T' command. + pos = 0 vi_state = event.cli.vi_state - + type = TextObjectType.EXCLUSIVE - if vi_state.last_character_find: - char = vi_state.last_character_find.character - backwards = vi_state.last_character_find.backwards - - if reverse: - backwards = not backwards - - if backwards: - pos = event.current_buffer.document.find_backwards(char, in_current_line=True, count=event.arg) - else: - pos = event.current_buffer.document.find(char, in_current_line=True, count=event.arg) + if vi_state.last_character_find: + char = vi_state.last_character_find.character + backwards = vi_state.last_character_find.backwards + + if reverse: + backwards = not backwards + + if backwards: + pos = event.current_buffer.document.find_backwards(char, in_current_line=True, count=event.arg) + else: + pos = event.current_buffer.document.find(char, in_current_line=True, count=event.arg) type = TextObjectType.INCLUSIVE if pos: return TextObject(pos, type=type) else: return TextObject(0) - repeat(True) - repeat(False) - + repeat(True) + repeat(False) + @text_object('h') @text_object(Keys.Left) - def _(event): - """ Implements 'ch', 'dh', 'h': Cursor left. """ + def _(event): + """ Implements 'ch', 'dh', 'h': Cursor left. """ return TextObject(event.current_buffer.document.get_cursor_left_position(count=event.arg)) - + @text_object('j', no_move_handler=True, no_selection_handler=True) # Note: We also need `no_selection_handler`, because we in # selection mode, we prefer the other 'j' binding that keeps # `buffer.preferred_column`. - def _(event): - """ Implements 'cj', 'dj', 'j', ... Cursor up. """ + def _(event): + """ Implements 'cj', 'dj', 'j', ... Cursor up. """ return TextObject(event.current_buffer.document.get_cursor_down_position(count=event.arg), type=TextObjectType.LINEWISE) - + @text_object('k', no_move_handler=True, no_selection_handler=True) - def _(event): - """ Implements 'ck', 'dk', 'k', ... Cursor up. """ + def _(event): + """ Implements 'ck', 'dk', 'k', ... Cursor up. """ return TextObject(event.current_buffer.document.get_cursor_up_position(count=event.arg), type=TextObjectType.LINEWISE) - + @text_object('l') @text_object(' ') @text_object(Keys.Right) - def _(event): - """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """ + def _(event): + """ Implements 'cl', 'dl', 'l', 'c ', 'd ', ' '. Cursor right. """ return TextObject(event.current_buffer.document.get_cursor_right_position(count=event.arg)) - + @text_object('H') - def _(event): - """ - Moves to the start of the visible region. (Below the scroll offset.) - Implements 'cH', 'dH', 'H'. - """ - w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) - b = event.current_buffer - + 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 and w.render_info: - # When we find a Window that has BufferControl showing this window, - # move to the start of the visible area. - pos = (b.document.translate_row_col_to_index( - w.render_info.first_visible_line(after_scroll_offset=True), 0) - - b.cursor_position) - - else: - # Otherwise, move to the start of the input. - pos = -len(b.document.text_before_cursor) + # When we find a Window that has BufferControl showing this window, + # move to the start of the visible area. + pos = (b.document.translate_row_col_to_index( + w.render_info.first_visible_line(after_scroll_offset=True), 0) - + b.cursor_position) + + else: + # Otherwise, move to the start of the input. + pos = -len(b.document.text_before_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) - + @text_object('M') - def _(event): - """ - Moves cursor to the vertical center of the visible region. - Implements 'cM', 'dM', 'M'. - """ - w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) - b = event.current_buffer - + 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 and w.render_info: - # When we find a Window that has BufferControl showing this window, - # move to the center of the visible area. - pos = (b.document.translate_row_col_to_index( - w.render_info.center_visible_line(), 0) - - b.cursor_position) - - else: - # Otherwise, move to the start of the input. - pos = -len(b.document.text_before_cursor) + # When we find a Window that has BufferControl showing this window, + # move to the center of the visible area. + pos = (b.document.translate_row_col_to_index( + w.render_info.center_visible_line(), 0) - + b.cursor_position) + + else: + # Otherwise, move to the start of the input. + pos = -len(b.document.text_before_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) - + @text_object('L') - def _(event): - """ - Moves to the end of the visible region. (Above the scroll offset.) - """ - w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) - b = event.current_buffer - + 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 and w.render_info: - # When we find a Window that has BufferControl showing this window, - # move to the end of the visible area. - pos = (b.document.translate_row_col_to_index( - w.render_info.last_visible_line(before_scroll_offset=True), 0) - - b.cursor_position) - - else: - # Otherwise, move to the end of the input. - pos = len(b.document.text_after_cursor) + # When we find a Window that has BufferControl showing this window, + # move to the end of the visible area. + pos = (b.document.translate_row_col_to_index( + w.render_info.last_visible_line(before_scroll_offset=True), 0) - + b.cursor_position) + + else: + # Otherwise, move to the end of the input. + pos = len(b.document.text_after_cursor) return TextObject(pos, type=TextObjectType.LINEWISE) - + @text_object('n', no_move_handler=True) def _(event): " Search next. " @@ -1374,50 +1374,50 @@ def load_vi_bindings(get_search_state=None): ~get_search_state(event.cli), include_current_position=False, count=event.arg) - @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 + @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 w.vertical_scroll = b.document.cursor_position_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) - + + @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) + # We can safely set the scroll offset to zero; the Window will meke # sure that it scrolls at least enough to make the cursor visible # again. w.vertical_scroll = 0 - - @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: + + @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: info = w.render_info - # Calculate the offset that we need in order to position the row - # containing the cursor in the center. + # Calculate the offset that we need in order to position the row + # containing the cursor in the center. scroll_height = info.window_height // 2 - + y = max(0, b.document.cursor_position_row - 1) height = 0 while y > 0: line_height = info.get_height_for_line(y) - + if height + line_height < scroll_height: height += line_height y -= 1 @@ -1427,81 +1427,81 @@ def load_vi_bindings(get_search_state=None): w.vertical_scroll = y @text_object('%') - def _(event): - """ - Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) - If an 'arg' has been given, go this this % position in the file. - """ - buffer = event.current_buffer - - if event._arg: - # If 'arg' has been given, the meaning of % is to go to the 'x%' - # row in the file. - if 0 < event.arg <= 100: - absolute_index = buffer.document.translate_row_col_to_index( + def _(event): + """ + Implements 'c%', 'd%', '%, 'y%' (Move to corresponding bracket.) + If an 'arg' has been given, go this this % position in the file. + """ + buffer = event.current_buffer + + if event._arg: + # If 'arg' has been given, the meaning of % is to go to the 'x%' + # row in the file. + if 0 < event.arg <= 100: + absolute_index = buffer.document.translate_row_col_to_index( int((event.arg * buffer.document.line_count - 1) / 100), 0) return TextObject(absolute_index - buffer.document.cursor_position, type=TextObjectType.LINEWISE) - else: + else: return TextObject(0) # Do nothing. - - else: - # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). + + else: + # Move to the corresponding opening/closing bracket (()'s, []'s and {}'s). match = buffer.document.find_matching_bracket_position() if match: return TextObject(match, type=TextObjectType.INCLUSIVE) else: return TextObject(0) - + @text_object('|') - def _(event): - # Move to the n-th column (you may specify the argument n by typing - # it on number keys, for example, 20|). + def _(event): + # Move to the n-th column (you may specify the argument n by typing + # it on number keys, for example, 20|). return TextObject(event.current_buffer.document.get_column_cursor_position(event.arg - 1)) - + @text_object('g', 'g') - def _(event): - """ - Implements 'gg', 'cgg', 'ygg' - """ - d = event.current_buffer.document - - if event._arg: - # Move to the given line. + def _(event): + """ + Implements 'gg', 'cgg', 'ygg' + """ + d = event.current_buffer.document + + if event._arg: + # Move to the given line. return TextObject(d.translate_row_col_to_index(event.arg - 1, 0) - d.cursor_position, type=TextObjectType.LINEWISE) - else: - # Move to the top of the input. + else: + # Move to the top of the input. return TextObject(d.get_start_of_document_position(), type=TextObjectType.LINEWISE) - + @text_object('g', '_') - def _(event): - """ - Go to last non-blank of line. - 'g_', 'cg_', 'yg_', etc.. - """ + def _(event): + """ + Go to last non-blank of line. + 'g_', 'cg_', 'yg_', etc.. + """ return TextObject( event.current_buffer.document.last_non_blank_of_current_line_position(), type=TextObjectType.INCLUSIVE) - + @text_object('g', 'e') - def _(event): - """ - Go to last character of previous word. - 'ge', 'cge', 'yge', etc.. - """ + def _(event): + """ + Go to last character of previous word. + 'ge', 'cge', 'yge', etc.. + """ prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg) return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) - + @text_object('g', 'E') - def _(event): - """ - Go to last character of previous WORD. - 'gE', 'cgE', 'ygE', etc.. - """ + def _(event): + """ + Go to last character of previous WORD. + 'gE', 'cgE', 'ygE', etc.. + """ prev_end = event.current_buffer.document.find_previous_word_ending(count=event.arg, WORD=True) return TextObject(prev_end - 1 if prev_end is not None else 0, type=TextObjectType.INCLUSIVE) - + @text_object('g', 'm') - def _(event): - """ + def _(event): + """ Like g0, but half a screenwidth to the right. (Or as much as possible.) """ w = find_window_for_buffer_name(event.cli, event.cli.current_buffer_name) @@ -1518,58 +1518,58 @@ def load_vi_bindings(get_search_state=None): @text_object('G') def _(event): """ - Go to the end of the document. (If no arg has been given.) - """ + Go to the end of the document. (If no arg has been given.) + """ buf = event.current_buffer return TextObject(buf.document.translate_row_col_to_index(buf.document.line_count - 1, 0) - buf.cursor_position, type=TextObjectType.LINEWISE) - + # # *** Other *** # - @handle('G', filter=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('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) + for n in '123456789': @handle(n, filter=navigation_mode|selection_mode|operator_given) def _(event): """ Always handle numberics in navigation mode as arg. """ - event.append_to_arg_count(event.data) - + event.append_to_arg_count(event.data) + @handle('0', filter=(navigation_mode|selection_mode|operator_given) & HasArg()) def _(event): " Zero when an argument was already give. " event.append_to_arg_count(event.data) - @handle(Keys.Any, filter=replace_mode) - def _(event): - """ - Insert data at cursor position. - """ - event.current_buffer.insert_text(event.data, overwrite=True) - + @handle(Keys.Any, filter=replace_mode) + def _(event): + """ + Insert data at cursor position. + """ + event.current_buffer.insert_text(event.data, overwrite=True) + @handle(Keys.Any, filter=insert_multiple_mode, save_before=(lambda e: not e.is_repeat)) def _(event): - """ + """ Insert data at multiple cursor positions at once. (Usually a result of pressing 'I' or 'A' in block-selection mode.) - """ + """ buff = event.current_buffer original_text = buff.text - + # Construct new text. text = [] p = 0 - + for p2 in buff.multiple_cursor_positions: text.append(original_text[p:p2]) text.append(event.data) @@ -1654,27 +1654,27 @@ def load_vi_bindings(get_search_state=None): event.cli.output.bell() - @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 - + @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 + @handle(Keys.ControlK, filter=insert_mode|replace_mode) def _(event): " Go into digraph mode. " event.cli.vi_state.waiting_for_digraph = True - + @Condition def digraph_symbol_1_given(cli): return cli.vi_state.digraph_symbol1 is not None @@ -1709,128 +1709,128 @@ def load_vi_bindings(get_search_state=None): def load_vi_open_in_editor_bindings(): - """ - Pressing 'v' in navigation mode will open the buffer in an external editor. - """ + """ + Pressing 'v' in navigation mode will open the buffer in an external editor. + """ registry = Registry() navigation_mode = ViNavigationMode() - + registry.add_binding('v', filter=navigation_mode)( get_by_name('edit-and-execute-command')) return registry - - + + def load_vi_system_bindings(): registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding - has_focus = filters.HasFocus(SYSTEM_BUFFER) + has_focus = filters.HasFocus(SYSTEM_BUFFER) navigation_mode = ViNavigationMode() - - @handle('!', filter=~has_focus & navigation_mode) - def _(event): - """ - '!' opens the system prompt. - """ - event.cli.push_focus(SYSTEM_BUFFER) + + @handle('!', filter=~has_focus & navigation_mode) + def _(event): + """ + '!' opens the system prompt. + """ + event.cli.push_focus(SYSTEM_BUFFER) event.cli.vi_state.input_mode = InputMode.INSERT - - @handle(Keys.Escape, filter=has_focus) - @handle(Keys.ControlC, filter=has_focus) - def _(event): - """ - Cancel system prompt. - """ + + @handle(Keys.Escape, filter=has_focus) + @handle(Keys.ControlC, filter=has_focus) + def _(event): + """ + Cancel system prompt. + """ event.cli.vi_state.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. - """ + event.cli.buffers[SYSTEM_BUFFER].reset() + event.cli.pop_focus() + + @handle(Keys.ControlJ, filter=has_focus) + def _(event): + """ + Run system command. + """ event.cli.vi_state.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() - + + 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() + return registry - + def load_vi_search_bindings(get_search_state=None, search_buffer_name=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 - + 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 + registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding - has_focus = filters.HasFocus(search_buffer_name) + has_focus = filters.HasFocus(search_buffer_name) navigation_mode = ViNavigationMode() selection_mode = ViSelectionMode() - + reverse_vi_search_direction = Condition( lambda cli: cli.application.reverse_vi_search_direction(cli)) @handle('/', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction) @handle('?', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction) - @handle(Keys.ControlS, filter=~has_focus) - def _(event): - """ - Vi-style forward search. - """ - # Set the ViState. - get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD + @handle(Keys.ControlS, filter=~has_focus) + def _(event): + """ + Vi-style forward search. + """ + # Set the ViState. + get_search_state(event.cli).direction = IncrementalSearchDirection.FORWARD event.cli.vi_state.input_mode = InputMode.INSERT - - # Focus search buffer. - event.cli.push_focus(search_buffer_name) - + + # Focus search buffer. + event.cli.push_focus(search_buffer_name) + @handle('?', filter=(navigation_mode|selection_mode)&~reverse_vi_search_direction) @handle('/', filter=(navigation_mode|selection_mode)&reverse_vi_search_direction) - @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) + @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) event.cli.vi_state.input_mode = InputMode.INSERT - - @handle(Keys.ControlJ, filter=has_focus) + + @handle(Keys.ControlJ, filter=has_focus) @handle(Keys.Escape, 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. + 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. event.cli.vi_state.input_mode = InputMode.NAVIGATION - event.cli.pop_focus() - + event.cli.pop_focus() + def incremental_search(cli, direction, count=1): " Apply search, but keep search buffer focussed. " # Update search_state. @@ -1854,41 +1854,41 @@ def load_vi_search_bindings(get_search_state=None, def _(event): incremental_search(event.cli, IncrementalSearchDirection.FORWARD, count=event.arg) - def search_buffer_is_empty(cli): - """ Returns True when the search buffer is empty. """ - return cli.buffers[search_buffer_name].text == '' - - @handle(Keys.ControlC, filter=has_focus) + def search_buffer_is_empty(cli): + """ Returns True when the search buffer is empty. """ + return cli.buffers[search_buffer_name].text == '' + + @handle(Keys.ControlC, filter=has_focus) @handle(Keys.ControlH, filter=has_focus & Condition(search_buffer_is_empty)) - @handle(Keys.Backspace, filter=has_focus & Condition(search_buffer_is_empty)) - def _(event): - """ - Cancel search. - """ + @handle(Keys.Backspace, filter=has_focus & Condition(search_buffer_is_empty)) + def _(event): + """ + Cancel search. + """ event.cli.vi_state.input_mode = InputMode.NAVIGATION - - event.cli.pop_focus() - event.cli.buffers[search_buffer_name].reset() - + + event.cli.pop_focus() + event.cli.buffers[search_buffer_name].reset() + return registry - + def load_extra_vi_page_navigation_bindings(): - """ - Key bindings, for scrolling up and down through pages. - This are separate bindings, because GNU readline doesn't have them. - """ + """ + Key bindings, for scrolling up and down through pages. + This are separate bindings, because GNU readline doesn't have them. + """ registry = ConditionalRegistry(Registry(), ViMode()) handle = registry.add_binding - - 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) + + 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) return registry diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/input_processor.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/input_processor.py index 51a3110827..a94acad31c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/input_processor.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/input_processor.py @@ -1,35 +1,35 @@ -# *** 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 +# *** 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`. -""" -from __future__ import unicode_literals -from prompt_toolkit.buffer import EditReadOnlyBuffer +""" +from __future__ import unicode_literals +from prompt_toolkit.buffer import EditReadOnlyBuffer from prompt_toolkit.filters.cli import ViNavigationMode from prompt_toolkit.keys import Keys, Key from prompt_toolkit.utils import Event - + from .registry import BaseRegistry from collections import deque from six.moves import range -import weakref +import weakref import six - -__all__ = ( - 'InputProcessor', - 'KeyPress', -) - - -class KeyPress(object): - """ + +__all__ = ( + 'InputProcessor', + 'KeyPress', +) + + +class KeyPress(object): + """ :param key: A `Keys` instance or text (one character). - :param data: The received string on stdin. (Often vt100 escape codes.) - """ + :param data: The received string on stdin. (Often vt100 escape codes.) + """ def __init__(self, key, data=None): assert isinstance(key, (six.text_type, Key)) assert data is None or isinstance(data, six.text_type) @@ -37,44 +37,44 @@ class KeyPress(object): if data is None: data = key.name if isinstance(key, Key) else key - self.key = key - self.data = data - - def __repr__(self): - return '%s(key=%r, data=%r)' % ( - self.__class__.__name__, self.key, self.data) - - def __eq__(self, other): - return self.key == other.key and self.data == other.data - - -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. + 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(KeyPress(Keys.ControlX, '\x18')) p.feed(KeyPress(Keys.ControlC, '\x03') - + # Process all the keys in the queue. p.process_keys() - # Now the ControlX-ControlC callback will be called if this sequence is - # registered in the registry. - + # Now the ControlX-ControlC callback will be called if this sequence is + # registered in the registry. + :param registry: `BaseRegistry` instance. - :param cli_ref: weakref to `CommandLineInterface`. - """ - def __init__(self, registry, cli_ref): + :param cli_ref: weakref to `CommandLineInterface`. + """ + def __init__(self, registry, cli_ref): assert isinstance(registry, BaseRegistry) - self._registry = registry - self._cli_ref = cli_ref + self._registry = registry + self._cli_ref = cli_ref self.beforeKeyPress = Event(self) self.afterKeyPress = Event(self) @@ -90,19 +90,19 @@ class InputProcessor(object): self.record_macro = False self.macro = [] - self.reset() - - def reset(self): + self.reset() + + def reset(self): self._previous_key_sequence = [] - 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 - + 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 start_macro(self): " Start recording macro. " self.record_macro = True @@ -116,106 +116,106 @@ class InputProcessor(object): for k in self.macro: self.feed(k) - 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 + 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 return [b for b in self._registry.get_bindings_for_keys(keys) if b.filter(cli)] - - 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. - """ + + 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 = self.key_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) - + 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 eager matches were found, give priority to them and also # ignore all the longer matches. eager_matches = [m for m in matches if m.eager(self._cli_ref())] if eager_matches: matches = eager_matches - is_prefix_of_longer_match = False - - # Exact matches found, call handler. - if not is_prefix_of_longer_match and matches: + is_prefix_of_longer_match = False + + # Exact matches found, call handler. + if not is_prefix_of_longer_match and matches: self._call_handler(matches[-1], key_sequence=buffer[:]) del buffer[:] # Keep reference. - - # No match found. - elif not is_prefix_of_longer_match and not matches: - retry = True - found = False - - # Loop over the input, try longest match first and shift. - for i in range(len(buffer), 0, -1): - matches = self._get_matches(buffer[:i]) - if matches: - self._call_handler(matches[-1], key_sequence=buffer[:i]) + + # No match found. + elif not is_prefix_of_longer_match and not matches: + retry = True + found = False + + # Loop over the input, try longest match first and shift. + for i in range(len(buffer), 0, -1): + matches = self._get_matches(buffer[:i]) + if matches: + self._call_handler(matches[-1], key_sequence=buffer[:i]) del buffer[:i] - found = True + found = True break - - if not found: + + if not found: del buffer[:1] - + def feed(self, key_press): - """ + """ Add a new :class:`KeyPress` to the input queue. (Don't forget to call `process_keys` in order to process the queue.) - """ - assert isinstance(key_press, KeyPress) + """ + assert isinstance(key_press, KeyPress) self.input_queue.append(key_press) - + def process_keys(self): """ Process all the keys in the `input_queue`. (To be called after `feed`.) - + Note: because of the `feed`/`process_keys` separation, it is possible to call `feed` from inside a key binding. This function keeps looping until the queue is empty. """ while self.input_queue: key_press = self.input_queue.popleft() - + if key_press.key != Keys.CPRResponse: self.beforeKeyPress.fire() - + self._process_coroutine.send(key_press) if key_press.key != Keys.CPRResponse: @@ -226,16 +226,16 @@ class InputProcessor(object): if cli: cli.invalidate() - def _call_handler(self, handler, key_sequence=None): + def _call_handler(self, handler, key_sequence=None): was_recording = self.record_macro - arg = self.arg - self.arg = None - + arg = self.arg + self.arg = None + event = KeyPressEvent( weakref.ref(self), arg=arg, key_sequence=key_sequence, previous_key_sequence=self._previous_key_sequence, is_repeat=(handler == self._previous_handler)) - + # Save the state of the current buffer. cli = event.cli # Can be `None` (In unit-tests only.) @@ -243,18 +243,18 @@ class InputProcessor(object): cli.current_buffer.save_to_undo_stack() # Call handler. - try: - handler.call(event) + try: + handler.call(event) self._fix_vi_cursor_position(event) - - except EditReadOnlyBuffer: + + 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 - + pass + + self._previous_key_sequence = key_sequence + self._previous_handler = handler + # Record the key sequence in our macro. (Only if we're in macro mode # before and after executing the key.) if self.record_macro and was_recording: @@ -266,16 +266,16 @@ class InputProcessor(object): never put the cursor after the last character of a line. (Unless it's an empty line.) """ - cli = self._cli_ref() + cli = self._cli_ref() if cli: buff = cli.current_buffer preferred_column = buff.preferred_column - + if (ViNavigationMode()(event.cli) and buff.document.is_cursor_at_the_end_of_line and len(buff.document.current_line) > 0): buff.cursor_position -= 1 - + # Set the preferred_column for arrow up/down again. # (This was cleared after changing the cursor position.) buff.preferred_column = preferred_column @@ -283,60 +283,60 @@ class InputProcessor(object): class KeyPressEvent(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): + """ + 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 'KeyPressEvent(arg=%r, key_sequence=%r, is_repeat=%r)' % ( - self.arg, self.key_sequence, self.is_repeat) - - @property - def data(self): - return self.key_sequence[-1].data - - @property - def 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. - """ + 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. + """ if self._arg == '-': return -1 - + result = int(self._arg or 1) # Don't exceed a million. @@ -352,21 +352,21 @@ class KeyPressEvent(object): """ return self._arg is not None - def append_to_arg_count(self, data): - """ - Add digit to the input argument. - - :param data: the typed digit as string - """ - assert data in '-0123456789' - current = self._arg - + def append_to_arg_count(self, data): + """ + Add digit to the input argument. + + :param data: the typed digit as string + """ + assert data in '-0123456789' + current = self._arg + if data == '-': assert current is None or current == '-' result = data elif current is None: result = data - else: + else: result = "%s%s" % (current, data) - - self.input_processor.arg = result + + self.input_processor.arg = result diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/manager.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/manager.py index 83612c2a5c..3e4bf7ff1f 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/manager.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/manager.py @@ -1,61 +1,61 @@ -""" +""" DEPRECATED: Use `prompt_toolkit.key_binding.defaults.load_key_bindings` instead. -: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() +: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() app = Application(key_bindings_registry=manager.registry) -""" -from __future__ import unicode_literals +""" +from __future__ import unicode_literals from .defaults import load_key_bindings -from prompt_toolkit.filters import to_cli_filter +from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.key_binding.registry import Registry, ConditionalRegistry, MergedRegistry - -__all__ = ( - 'KeyBindingManager', -) - - -class KeyBindingManager(object): - """ - Utility for loading all key bindings into memory. - - :param registry: Optional `Registry` instance. - :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. + +__all__ = ( + 'KeyBindingManager', +) + + +class KeyBindingManager(object): + """ + Utility for loading all key bindings into memory. + + :param registry: Optional `Registry` instance. + :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_vi_mode: Deprecated! - """ + """ def __init__(self, registry=None, # XXX: not used anymore. enable_vi_mode=None, # (`enable_vi_mode` is deprecated.) enable_all=True, # - get_search_state=None, - enable_abort_and_exit_bindings=False, + 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): - - assert registry is None or isinstance(registry, Registry) - assert get_search_state is None or callable(get_search_state) - enable_all = to_cli_filter(enable_all) - + + assert registry is None or isinstance(registry, Registry) + assert get_search_state is None or callable(get_search_state) + enable_all = to_cli_filter(enable_all) + defaults = load_key_bindings( get_search_state=get_search_state, enable_abort_and_exit_bindings=enable_abort_and_exit_bindings, @@ -64,30 +64,30 @@ class KeyBindingManager(object): enable_open_in_editor=enable_open_in_editor, enable_extra_page_navigation=enable_extra_page_navigation, enable_auto_suggest_bindings=enable_auto_suggest_bindings) - + # Note, we wrap this whole thing again in a MergedRegistry, because we # don't want the `enable_all` settings to apply on items that were # added to the registry as a whole. self.registry = MergedRegistry([ ConditionalRegistry(defaults, enable_all) ]) - - @classmethod - def for_prompt(cls, **kw): - """ - Create a ``KeyBindingManager`` with the defaults for an input prompt. + + @classmethod + def for_prompt(cls, **kw): + """ + Create a ``KeyBindingManager`` with the defaults for an input prompt. This activates the key 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): + 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): # For backwards compatibility. pass diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/registry.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/registry.py index 24d0e729a1..5df089b20c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/registry.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/registry.py @@ -23,110 +23,110 @@ others contain the Vi bindings. They are merged together using a We also have a `ConditionalRegistry` object that can enable/disable a group of key bindings at once. """ -from __future__ import unicode_literals +from __future__ import unicode_literals from abc import ABCMeta, abstractmethod from prompt_toolkit.cache import SimpleCache from prompt_toolkit.filters import CLIFilter, to_cli_filter, Never from prompt_toolkit.keys import Key, Keys - + from six import text_type, with_metaclass - -__all__ = ( + +__all__ = ( 'BaseRegistry', - 'Registry', + 'Registry', 'ConditionalRegistry', 'MergedRegistry', -) - - -class _Binding(object): - """ - (Immutable binding class.) - """ +) + + +class _Binding(object): + """ + (Immutable binding class.) + """ def __init__(self, keys, handler, filter=None, eager=None, save_before=None): - assert isinstance(keys, tuple) - assert callable(handler) - assert isinstance(filter, CLIFilter) - assert isinstance(eager, CLIFilter) + assert isinstance(keys, tuple) + assert callable(handler) + assert isinstance(filter, CLIFilter) + assert isinstance(eager, CLIFilter) assert callable(save_before) - - self.keys = keys - self.handler = handler - self.filter = filter - self.eager = eager + + self.keys = keys + self.handler = handler + self.filter = filter + self.eager = eager self.save_before = save_before - - def call(self, event): - return self.handler(event) - - def __repr__(self): - return '%s(keys=%r, handler=%r)' % ( - self.__class__.__name__, self.keys, self.handler) - - + + 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 BaseRegistry(with_metaclass(ABCMeta, object)): - """ + """ Interface for a Registry. """ _version = 0 # For cache invalidation. - + @abstractmethod def get_bindings_for_keys(self, keys): pass - + @abstractmethod def get_bindings_starting_with_keys(self, keys): pass - + # `add_binding` and `remove_binding` don't have to be part of this # interface. class Registry(BaseRegistry): - """ + """ Key binding registry. """ - def __init__(self): - self.key_bindings = [] + def __init__(self): + self.key_bindings = [] self._get_bindings_for_keys_cache = SimpleCache(maxsize=10000) self._get_bindings_starting_with_keys_cache = SimpleCache(maxsize=1000) self._version = 0 # For cache invalidation. - + def _clear_cache(self): self._version += 1 self._get_bindings_for_keys_cache.clear() self._get_bindings_starting_with_keys_cache.clear() - - 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. + + 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 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.) - """ - filter = to_cli_filter(kwargs.pop('filter', True)) - eager = to_cli_filter(kwargs.pop('eager', False)) + """ + filter = to_cli_filter(kwargs.pop('filter', True)) + eager = to_cli_filter(kwargs.pop('eager', False)) save_before = kwargs.pop('save_before', lambda e: True) to_cli_filter(kwargs.pop('invalidate_ui', True)) # Deprecated! (ignored.) - - 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.' + + 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.' assert callable(save_before) - + if isinstance(filter, Never): - # When a filter is Never, it will always stay disabled, so in that case - # don't bother putting it in the registry. It will slow down every key + # 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. def decorator(func): return func @@ -136,44 +136,44 @@ class Registry(BaseRegistry): _Binding(keys, func, filter=filter, eager=eager, save_before=save_before)) self._clear_cache() - + 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) + 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._clear_cache() - 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 + + # 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. + """ def get(): result = [] for b in self.key_bindings: if len(keys) == len(b.keys): match = True any_count = 0 - + for i, j in zip(b.keys, keys): if i != j and i != Keys.Any: match = False @@ -192,15 +192,15 @@ class Registry(BaseRegistry): return self._get_bindings_for_keys_cache.get(keys, get) - def get_bindings_starting_with_keys(self, keys): - """ - Return a list of key bindings that handle a key sequence starting with - `keys`. (It does only return bindings for which the sequences are - longer than `keys`. And like `get_bindings_for_keys`, it also includes - inactive bindings.) - - :param keys: tuple of keys. - """ + def get_bindings_starting_with_keys(self, keys): + """ + Return a list of key bindings that handle a key sequence starting with + `keys`. (It does only return bindings for which the sequences are + longer than `keys`. And like `get_bindings_for_keys`, it also includes + inactive bindings.) + + :param keys: tuple of keys. + """ def get(): result = [] for b in self.key_bindings: diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/vi_state.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/vi_state.py index 92ce3cbd29..c6e3e366b0 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/vi_state.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/key_binding/vi_state.py @@ -1,34 +1,34 @@ -from __future__ import unicode_literals - -__all__ = ( - 'InputMode', - 'CharacterFind', - 'ViState', -) - - -class InputMode(object): - INSERT = 'vi-insert' +from __future__ import unicode_literals + +__all__ = ( + 'InputMode', + 'CharacterFind', + 'ViState', +) + + +class InputMode(object): + INSERT = 'vi-insert' INSERT_MULTIPLE = 'vi-insert-multiple' - 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 - + 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 + # When an operator is given and we are waiting for text object, # -- e.g. in the case of 'dw', after the 'd' --, an operator callback # is set here. @@ -39,9 +39,9 @@ class ViState(object): #: :class:`ClipboardData` instances. self.named_registers = {} - #: The Vi mode we're currently in to. - self.input_mode = InputMode.INSERT - + #: The Vi mode we're currently in to. + self.input_mode = InputMode.INSERT + #: Waiting for digraph. self.waiting_for_digraph = False self.digraph_symbol1 = None # (None or a symbol.) @@ -53,7 +53,7 @@ class ViState(object): """ Reset state, go back to the given mode. INSERT by default. """ - # Go back to insert mode. + # Go back to insert mode. self.input_mode = mode self.waiting_for_digraph = False diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/keys.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/keys.py index d5df9bff41..ffa11f813e 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/keys.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/keys.py @@ -1,128 +1,128 @@ -from __future__ import unicode_literals - -__all__ = ( - 'Key', - 'Keys', -) - - -class Key(object): - def __init__(self, name): - - #: Descriptive way of writing keys in configuration files. e.g. <C-A> - #: for ``Control-A``. - self.name = name - - def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, self.name) - - -class Keys(object): - Escape = Key('<Escape>') - - ControlA = Key('<C-A>') - ControlB = Key('<C-B>') - ControlC = Key('<C-C>') - ControlD = Key('<C-D>') - ControlE = Key('<C-E>') - ControlF = Key('<C-F>') - ControlG = Key('<C-G>') - ControlH = Key('<C-H>') - ControlI = Key('<C-I>') # Tab - ControlJ = Key('<C-J>') # Enter - ControlK = Key('<C-K>') - ControlL = Key('<C-L>') - ControlM = Key('<C-M>') # Enter - ControlN = Key('<C-N>') - ControlO = Key('<C-O>') - ControlP = Key('<C-P>') - ControlQ = Key('<C-Q>') - ControlR = Key('<C-R>') - ControlS = Key('<C-S>') - ControlT = Key('<C-T>') - ControlU = Key('<C-U>') - ControlV = Key('<C-V>') - ControlW = Key('<C-W>') - ControlX = Key('<C-X>') - ControlY = Key('<C-Y>') - ControlZ = Key('<C-Z>') - - ControlSpace = Key('<C-Space>') - ControlBackslash = Key('<C-Backslash>') - ControlSquareClose = Key('<C-SquareClose>') - ControlCircumflex = Key('<C-Circumflex>') - ControlUnderscore = Key('<C-Underscore>') - ControlLeft = Key('<C-Left>') - ControlRight = Key('<C-Right>') - ControlUp = Key('<C-Up>') - ControlDown = Key('<C-Down>') - - Up = Key('<Up>') - Down = Key('<Down>') - Right = Key('<Right>') - Left = Key('<Left>') +from __future__ import unicode_literals + +__all__ = ( + 'Key', + 'Keys', +) + + +class Key(object): + def __init__(self, name): + + #: Descriptive way of writing keys in configuration files. e.g. <C-A> + #: for ``Control-A``. + self.name = name + + def __repr__(self): + return '%s(%r)' % (self.__class__.__name__, self.name) + + +class Keys(object): + Escape = Key('<Escape>') + + ControlA = Key('<C-A>') + ControlB = Key('<C-B>') + ControlC = Key('<C-C>') + ControlD = Key('<C-D>') + ControlE = Key('<C-E>') + ControlF = Key('<C-F>') + ControlG = Key('<C-G>') + ControlH = Key('<C-H>') + ControlI = Key('<C-I>') # Tab + ControlJ = Key('<C-J>') # Enter + ControlK = Key('<C-K>') + ControlL = Key('<C-L>') + ControlM = Key('<C-M>') # Enter + ControlN = Key('<C-N>') + ControlO = Key('<C-O>') + ControlP = Key('<C-P>') + ControlQ = Key('<C-Q>') + ControlR = Key('<C-R>') + ControlS = Key('<C-S>') + ControlT = Key('<C-T>') + ControlU = Key('<C-U>') + ControlV = Key('<C-V>') + ControlW = Key('<C-W>') + ControlX = Key('<C-X>') + ControlY = Key('<C-Y>') + ControlZ = Key('<C-Z>') + + ControlSpace = Key('<C-Space>') + ControlBackslash = Key('<C-Backslash>') + ControlSquareClose = Key('<C-SquareClose>') + ControlCircumflex = Key('<C-Circumflex>') + ControlUnderscore = Key('<C-Underscore>') + ControlLeft = Key('<C-Left>') + ControlRight = Key('<C-Right>') + ControlUp = Key('<C-Up>') + ControlDown = Key('<C-Down>') + + Up = Key('<Up>') + Down = Key('<Down>') + Right = Key('<Right>') + Left = Key('<Left>') ShiftLeft = Key('<ShiftLeft>') ShiftUp = Key('<ShiftUp>') ShiftDown = Key('<ShiftDown>') ShiftRight = Key('<ShiftRight>') - Home = Key('<Home>') - End = Key('<End>') - Delete = Key('<Delete>') - ShiftDelete = Key('<ShiftDelete>') + Home = Key('<Home>') + End = Key('<End>') + Delete = Key('<Delete>') + ShiftDelete = Key('<ShiftDelete>') ControlDelete = Key('<C-Delete>') - PageUp = Key('<PageUp>') - PageDown = Key('<PageDown>') - BackTab = Key('<BackTab>') # shift + tab - Insert = Key('<Insert>') + PageUp = Key('<PageUp>') + PageDown = Key('<PageDown>') + BackTab = Key('<BackTab>') # shift + tab + Insert = Key('<Insert>') Backspace = Key('<Backspace>') - + # Aliases. - Tab = ControlI + Tab = ControlI Enter = ControlJ # XXX: Actually Enter equals ControlM, not ControlJ, # However, in prompt_toolkit, we made the mistake of translating # \r into \n during the input, so everyone is now handling the # enter key by binding ControlJ. - + # From now on, it's better to bind `Keys.Enter` everywhere, # because that's future compatible, and will still work when we # stop replacing \r by \n. - F1 = Key('<F1>') - F2 = Key('<F2>') - F3 = Key('<F3>') - F4 = Key('<F4>') - F5 = Key('<F5>') - F6 = Key('<F6>') - F7 = Key('<F7>') - F8 = Key('<F8>') - F9 = Key('<F9>') - F10 = Key('<F10>') - F11 = Key('<F11>') - F12 = Key('<F12>') - F13 = Key('<F13>') - F14 = Key('<F14>') - F15 = Key('<F15>') - F16 = Key('<F16>') - F17 = Key('<F17>') - F18 = Key('<F18>') - F19 = Key('<F19>') - F20 = Key('<F20>') + F1 = Key('<F1>') + F2 = Key('<F2>') + F3 = Key('<F3>') + F4 = Key('<F4>') + F5 = Key('<F5>') + F6 = Key('<F6>') + F7 = Key('<F7>') + F8 = Key('<F8>') + F9 = Key('<F9>') + F10 = Key('<F10>') + F11 = Key('<F11>') + F12 = Key('<F12>') + F13 = Key('<F13>') + F14 = Key('<F14>') + F15 = Key('<F15>') + F16 = Key('<F16>') + F17 = Key('<F17>') + F18 = Key('<F18>') + F19 = Key('<F19>') + F20 = Key('<F20>') F21 = Key('<F21>') F22 = Key('<F22>') F23 = Key('<F23>') F24 = Key('<F24>') - - # Matches any key. - Any = Key('<Any>') - - # Special - CPRResponse = Key('<Cursor-Position-Response>') - Vt100MouseEvent = Key('<Vt100-Mouse-Event>') - WindowsMouseEvent = Key('<Windows-Mouse-Event>') - BracketedPaste = Key('<Bracketed-Paste>') + + # Matches any key. + Any = Key('<Any>') + + # Special + CPRResponse = Key('<Cursor-Position-Response>') + Vt100MouseEvent = Key('<Vt100-Mouse-Event>') + WindowsMouseEvent = Key('<Windows-Mouse-Event>') + BracketedPaste = Key('<Bracketed-Paste>') # Key which is ignored. (The key binding for this key should not do # anything.) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/__init__.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/__init__.py index 0dec5ecfaf..01ba850d6c 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/__init__.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/__init__.py @@ -1,51 +1,51 @@ -""" -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. +""" +Command line layout definitions +------------------------------- + +The layout of a command line interface is defined by a Container instance. +There are two main groups of classes here. Containers and controls: + +- A container can contain other containers or controls, it can have multiple + children and it decides about the dimensions. +- A control is responsible for rendering the actual content to a screen. A control can propose some dimensions, but it's the container who decides - about the dimensions -- or when the control consumes more space -- which part - of the control will be visible. - - -Container classes:: - - - Container (Abstract base class) - |- HSplit (Horizontal split) - |- VSplit (Vertical split) - |- FloatContainer (Container which can also contain menus and other floats) - `- Window (Container which contains one actual control - -Control classes:: - - - UIControl (Abstract base class) - |- 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 + about the dimensions -- or when the control consumes more space -- which part + of the control will be visible. + + +Container classes:: + + - Container (Abstract base class) + |- HSplit (Horizontal split) + |- VSplit (Vertical split) + |- FloatContainer (Container which can also contain menus and other floats) + `- Window (Container which contains one actual control + +Control classes:: + + - UIControl (Abstract base class) + |- 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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py index 0bdafe18e0..512c9122d0 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/containers.py @@ -1,16 +1,16 @@ -""" -Container for the layout. -(Containers can contain other containers or user interface controls.) -""" -from __future__ import unicode_literals - -from abc import ABCMeta, abstractmethod -from six import with_metaclass +""" +Container for the layout. +(Containers can contain other containers or user interface controls.) +""" +from __future__ import unicode_literals + +from abc import ABCMeta, abstractmethod +from six import with_metaclass from six.moves import range - + from .controls import UIControl, TokenListControl, UIContent -from .dimension import LayoutDimension, sum_layout_dimensions, max_layout_dimensions -from .margins import Margin +from .dimension import LayoutDimension, sum_layout_dimensions, max_layout_dimensions +from .margins import Margin from .screen import Point, WritePosition, _CHAR_CACHE from .utils import token_list_to_text, explode_tokens from prompt_toolkit.cache import SimpleCache @@ -19,495 +19,495 @@ from prompt_toolkit.mouse_events import MouseEvent, MouseEventType from prompt_toolkit.reactive import Integer from prompt_toolkit.token import Token from prompt_toolkit.utils import take_using_weights, get_cwidth - -__all__ = ( - 'Container', - 'HSplit', - 'VSplit', - 'FloatContainer', - 'Float', - 'Window', - 'WindowRenderInfo', - 'ConditionalContainer', + +__all__ = ( + 'Container', + 'HSplit', + 'VSplit', + 'FloatContainer', + 'Float', + 'Window', + 'WindowRenderInfo', + 'ConditionalContainer', 'ScrollOffsets', 'ColorColumn', -) - -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 +) + +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, max_available_height): - """ - 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) - + """ + 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, max_available_height): dimensions = [c.preferred_height(cli, width, max_available_height) 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 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, write_position.extended_height) - - 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) - + + 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, max_available_height): - sizes = self._divide_widths(cli, width) - if sizes is None: - return LayoutDimension() - else: + sizes = self._divide_widths(cli, width) + if sizes is None: + return LayoutDimension() + else: dimensions = [c.preferred_height(cli, s, max_available_height) - 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. + 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, write_position.extended_height).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) - + 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, max_available_height): - """ - Return the preferred height of the float container. - (We don't care about the height of the floats, they should always fit - into the dimensions provided by the container.) - """ + """ + Return 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, max_available_height) - - 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: + + 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, write_position.extended_height).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: + + # 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, write_position.extended_height).preferred - - if fl.top is not None: - ypos = fl.top - elif fl.bottom is not None: - ypos = max(0, write_position.height - height - fl.bottom) - else: # Center vertically. - ypos = max(0, int((write_position.height - height) / 2)) - - # Trim. - height = min(height, write_position.height - ypos) - - # Write float. - # (xpos and ypos can be negative: a float can be partially visible.) - if height > 0 and width > 0: - wp = WritePosition(xpos=xpos + write_position.xpos, - ypos=ypos + write_position.ypos, - width=width, height=height) - + + if fl.top is not None: + ypos = fl.top + elif fl.bottom is not None: + ypos = max(0, write_position.height - height - fl.bottom) + else: # Center vertically. + ypos = max(0, int((write_position.height - height) / 2)) + + # Trim. + height = min(height, write_position.height - ypos) + + # Write float. + # (xpos and ypos can be negative: a float can be partially visible.) + if height > 0 and width > 0: + wp = WritePosition(xpos=xpos + write_position.xpos, + ypos=ypos + write_position.ypos, + width=width, height=height) + if not fl.hide_when_covering_content or self._area_is_empty(screen, wp): fl.content.write_to_screen(cli, screen, mouse_handlers, wp) @@ -530,80 +530,80 @@ class FloatContainer(Container): return True - 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 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. :param hide_when_covering_content: Hide the float when it covers content underneath. - """ - def __init__(self, top=None, right=None, bottom=None, left=None, - width=None, height=None, get_width=None, get_height=None, + """ + 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, hide_when_covering_content=False): - 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 + 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 self.hide_when_covering_content = hide_when_covering_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.) - + + 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 ui_content: The original :class:`.UIContent` instance that contains the whole input, without clipping. (ui_content) - :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. - :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. + :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. + :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. :param window_width: The width of the window that displays the content, without the margins. :param window_height: The height of the window that displays the content. @@ -615,7 +615,7 @@ class WindowRenderInfo(object): :param rowcol_to_yx: Mapping that maps (row, column) tuples representing coordinates of the :class:`UIContent` to (y, x) absolute coordinates at the rendered screen. - """ + """ def __init__(self, ui_content, horizontal_scroll, vertical_scroll, window_width, window_height, configured_scroll_offsets, @@ -634,20 +634,20 @@ class WindowRenderInfo(object): assert isinstance(wrap_lines, bool) self.ui_content = ui_content - self.vertical_scroll = vertical_scroll + self.vertical_scroll = vertical_scroll self.window_width = window_width # Width without margins. - self.window_height = window_height + self.window_height = window_height - self.configured_scroll_offsets = configured_scroll_offsets + self.configured_scroll_offsets = configured_scroll_offsets self.visible_line_to_row_col = visible_line_to_row_col self.wrap_lines = wrap_lines - + self._rowcol_to_yx = rowcol_to_yx # row/col from input to absolute y/x # screen coordinates. self._x_offset = x_offset self._y_offset = y_offset - @property + @property def visible_line_to_input_line(self): return dict( (visible_line, rowcol[0]) @@ -655,29 +655,29 @@ class WindowRenderInfo(object): @property def cursor_position(self): - """ + """ Return the cursor position coordinates, relative to the left/top corner of the rendered screen. - """ + """ cpos = self.ui_content.cursor_position y, x = self._rowcol_to_yx[cpos.y, cpos.x] return Point(x=x - self._x_offset, y=y - self._y_offset) - - @property + + @property def applied_scroll_offsets(self): - """ + """ Return a :class:`.ScrollOffsets` instance that indicates the actual offset. This can be less than or equal to what's configured. E.g, when the cursor is completely at the top, the top offset will be zero rather than what's configured. - """ + """ if self.displayed_lines[0] == 0: top = 0 else: # Get row where the cursor is displayed. y = self.input_line_to_visible_line[self.ui_content.cursor_position.y] top = min(y, self.configured_scroll_offsets.top) - + return ScrollOffsets( top=top, bottom=min(self.ui_content.line_count - self.displayed_lines[-1] - 1, @@ -688,14 +688,14 @@ class WindowRenderInfo(object): # double width characters in mind.) left=0, right=0) - @property + @property def displayed_lines(self): - """ + """ List of all the visible rows. (Line numbers of the input buffer.) The last line may not be entirely visible. - """ + """ return sorted(row for row, col in self.visible_line_to_row_col.values()) - + @property def input_line_to_visible_line(self): """ @@ -711,74 +711,74 @@ class WindowRenderInfo(object): result[v] = k return result - def first_visible_line(self, after_scroll_offset=False): - """ - Return the line number (0 based) of the input document that corresponds - with the first visible line. - """ - if after_scroll_offset: + def first_visible_line(self, after_scroll_offset=False): + """ + Return the line number (0 based) of the input document that corresponds + with the first visible line. + """ + if after_scroll_offset: return self.displayed_lines[self.applied_scroll_offsets.top] else: return self.displayed_lines[0] - - def last_visible_line(self, before_scroll_offset=False): - """ - Like `first_visible_line`, but for the last visible line. - """ - if before_scroll_offset: + + def last_visible_line(self, before_scroll_offset=False): + """ + Like `first_visible_line`, but for the last visible line. + """ + if before_scroll_offset: return self.displayed_lines[-1 - self.applied_scroll_offsets.bottom] else: return self.displayed_lines[-1] - - def center_visible_line(self, before_scroll_offset=False, - after_scroll_offset=False): - """ - Like `first_visible_line`, but for the center visible line. - """ - return (self.first_visible_line(after_scroll_offset) + - (self.last_visible_line(before_scroll_offset) - + + 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. - """ + ) + + @property + def content_height(self): + """ + The full height of the user control. + """ return self.ui_content.line_count - - @property - def full_height_visible(self): - """ - True when the full height is visible (There is no vertical scroll.) - """ + + @property + def full_height_visible(self): + """ + True when the full height is visible (There is no vertical scroll.) + """ return self.vertical_scroll == 0 and self.last_visible_line() == self.content_height - - @property - def top_visible(self): - """ - True when the top of the buffer is visible. - """ - return self.vertical_scroll == 0 - - @property - def bottom_visible(self): - """ - True when the bottom of the buffer is visible. - """ + + @property + def top_visible(self): + """ + True when the top of the buffer is visible. + """ + return self.vertical_scroll == 0 + + @property + def bottom_visible(self): + """ + True when the bottom of the buffer is visible. + """ return self.last_visible_line() == self.content_height - 1 - - @property - def vertical_scroll_percentage(self): - """ - Vertical scroll as a percentage. (0 means: the top is visible, - 100 means: the bottom is visible.) - """ + + @property + def vertical_scroll_percentage(self): + """ + Vertical scroll as a percentage. (0 means: the top is visible, + 100 means: the bottom is visible.) + """ if self.bottom_visible: return 100 else: return (100 * self.vertical_scroll // self.content_height) - + def get_height_for_line(self, lineno): """ Return the height of the given line. @@ -788,20 +788,20 @@ class WindowRenderInfo(object): return self.ui_content.get_height_for_line(lineno, self.window_width) else: return 1 - - -class ScrollOffsets(object): - """ - Scroll offsets for the :class:`.Window` class. - - Note that left/right offsets only make sense if line wrapping is disabled. - """ - def __init__(self, top=0, bottom=0, left=0, right=0): + + +class ScrollOffsets(object): + """ + Scroll offsets for the :class:`.Window` class. + + Note that left/right offsets only make sense if line wrapping is disabled. + """ + def __init__(self, top=0, bottom=0, left=0, right=0): assert isinstance(top, Integer) assert isinstance(bottom, Integer) assert isinstance(left, Integer) assert isinstance(right, Integer) - + self._top = top self._bottom = bottom self._left = left @@ -823,11 +823,11 @@ class ScrollOffsets(object): def right(self): return int(self._right) - def __repr__(self): - return 'ScrollOffsets(top=%r, bottom=%r, left=%r, right=%r)' % ( - self.top, self.bottom, self.left, self.right) - - + def __repr__(self): + return 'ScrollOffsets(top=%r, bottom=%r, left=%r, right=%r)' % ( + self.top, self.bottom, self.left, self.right) + + class ColorColumn(object): def __init__(self, position, token=Token.ColorColumn): self.position = position @@ -837,46 +837,46 @@ class ColorColumn(object): _in_insert_mode = ViInsertMode() | EmacsInsertMode() -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. +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 wrap_lines: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, don't scroll horizontally, but wrap lines instead. - :param get_vertical_scroll: Callable that takes this window - instance as input and returns a preferred vertical scroll. - (When this is `None`, the scroll is only determined by the last and - current cursor position.) - :param get_horizontal_scroll: Callable that takes this window - instance as input and returns a preferred vertical scroll. - :param always_hide_cursor: A `bool` or - :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, never - display the cursor, even when the user control specifies a cursor - position. + :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. :param cursorline: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` instance. When True, display a cursorline. :param cursorcolumn: A `bool` or :class:`~prompt_toolkit.filters.CLIFilter` @@ -888,73 +888,73 @@ class Window(Container): if `cursorline` is True. :param cursorcolumn_token: The token to be used for highlighting the current line, if `cursorcolumn` is True. - """ - 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, + """ + 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, wrap_lines=False, get_vertical_scroll=None, get_horizontal_scroll=None, always_hide_cursor=False, cursorline=False, cursorcolumn=False, get_colorcolumns=None, cursorline_token=Token.CursorLine, cursorcolumn_token=Token.CursorColumn): - 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) + 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) assert get_colorcolumns is None or callable(get_colorcolumns) - - self.allow_scroll_beyond_bottom = to_cli_filter(allow_scroll_beyond_bottom) - self.always_hide_cursor = to_cli_filter(always_hide_cursor) + + self.allow_scroll_beyond_bottom = to_cli_filter(allow_scroll_beyond_bottom) + self.always_hide_cursor = to_cli_filter(always_hide_cursor) self.wrap_lines = to_cli_filter(wrap_lines) self.cursorline = to_cli_filter(cursorline) self.cursorcolumn = to_cli_filter(cursorcolumn) - - 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) + + 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) self.get_colorcolumns = get_colorcolumns or (lambda cli: []) self.cursorline_token = cursorline_token self.cursorcolumn_token = cursorcolumn_token - - # Cache for the screens generated by the margin. + + # Cache for the screens generated by the margin. self._ui_content_cache = SimpleCache(maxsize=8) self._margin_width_cache = SimpleCache(maxsize=1) - - self.reset() - - def __repr__(self): - return 'Window(content=%r)' % self.content - - def reset(self): - self.content.reset() - - #: Scrolling position of the main content. - self.vertical_scroll = 0 - self.horizontal_scroll = 0 - + + self.reset() + + def __repr__(self): + return 'Window(content=%r)' % self.content + + def reset(self): + self.content.reset() + + #: Scrolling position of the main content. + self.vertical_scroll = 0 + self.horizontal_scroll = 0 + # Vertical scroll 2: this is the vertical offset that a line is # scrolled if a single line (the one that contains the cursor) consumes # all of the vertical space. self.vertical_scroll_2 = 0 - #: Keep render information (mappings between buffer input and render - #: output.) - self.render_info = None - + #: Keep render information (mappings between buffer input and render + #: output.) + self.render_info = None + def _get_margin_width(self, cli, margin): """ Return the width for this margin. @@ -970,70 +970,70 @@ class Window(Container): key = (margin, cli.render_counter) return self._margin_width_cache.get(key, get_width) - def preferred_width(self, cli, max_available_width): + def preferred_width(self, cli, max_available_width): # Calculate the width of the margin. total_margin_width = sum(self._get_margin_width(cli, m) for m in - self.left_margins + self.right_margins) - + self.left_margins + self.right_margins) + # Window of the content. (Can be `None`.) - preferred_width = self.content.preferred_width( - cli, max_available_width - total_margin_width) - - if preferred_width is not None: + preferred_width = self.content.preferred_width( + cli, max_available_width - total_margin_width) + + if preferred_width is not None: # Include width of the margins. - preferred_width += total_margin_width - - # Merge. - return self._merge_dimensions( - dimension=self._width(cli), - preferred=preferred_width, - dont_extend=self.dont_extend_width) - + 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, max_available_height): total_margin_width = sum(self._get_margin_width(cli, m) for m in self.left_margins + self.right_margins) wrap_lines = self.wrap_lines(cli) - return self._merge_dimensions( - dimension=self._height(cli), + return self._merge_dimensions( + dimension=self._height(cli), preferred=self.content.preferred_height( cli, width - total_margin_width, max_available_height, wrap_lines), - 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 - + 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, weight=dimension.weight) - + def _get_ui_content(self, cli, width, height): """ Create a `UIContent` instance. @@ -1054,28 +1054,28 @@ class Window(Container): return '?' return False - 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. + 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 = [self._get_margin_width(cli, m) for m in self.left_margins] right_margin_widths = [self._get_margin_width(cli, m) for m in self.right_margins] - total_margin_width = sum(left_margin_widths + right_margin_widths) - - # Render UserControl. + total_margin_width = sum(left_margin_widths + right_margin_widths) + + # Render UserControl. ui_content = self.content.create_content( - cli, write_position.width - total_margin_width, write_position.height) + cli, write_position.width - total_margin_width, write_position.height) assert isinstance(ui_content, UIContent) - - # Scroll content. + + # Scroll content. wrap_lines = self.wrap_lines(cli) scroll_func = self._scroll_when_linewrapping if wrap_lines else self._scroll_without_linewrapping - + scroll_func( ui_content, write_position.width - total_margin_width, write_position.height, cli) - + # Write body visible_line_to_row_col, rowcol_to_yx = self._copy_body( cli, ui_content, screen, write_position, @@ -1086,37 +1086,37 @@ class Window(Container): vertical_scroll_2=self.vertical_scroll_2, always_hide_cursor=self.always_hide_cursor(cli)) - # Remember render info. (Set before generating the margins. They need this.) + # Remember render info. (Set before generating the margins. They need this.) x_offset=write_position.xpos + sum(left_margin_widths) y_offset=write_position.ypos - self.render_info = WindowRenderInfo( + self.render_info = WindowRenderInfo( ui_content=ui_content, - horizontal_scroll=self.horizontal_scroll, - vertical_scroll=self.vertical_scroll, + horizontal_scroll=self.horizontal_scroll, + vertical_scroll=self.vertical_scroll, window_width=write_position.width - total_margin_width, - window_height=write_position.height, - configured_scroll_offsets=self.scroll_offsets, + window_height=write_position.height, + configured_scroll_offsets=self.scroll_offsets, visible_line_to_row_col=visible_line_to_row_col, rowcol_to_yx=rowcol_to_yx, x_offset=x_offset, y_offset=y_offset, wrap_lines=wrap_lines) - - # Set mouse handlers. - def mouse_handler(cli, mouse_event): - """ Wrapper around the mouse_handler of the `UIControl` that turns + + # Set mouse handlers. + def mouse_handler(cli, mouse_event): + """ Wrapper around the mouse_handler of the `UIControl` that turns screen coordinates into line coordinates. """ # Find row/col position first. yx_to_rowcol = dict((v, k) for k, v in rowcol_to_yx.items()) y = mouse_event.position.y x = mouse_event.position.x - + # If clicked below the content area, look for a position in the # last line instead. max_y = write_position.ypos + len(visible_line_to_row_col) - 1 y = min(max_y, y) - + while x >= 0: try: row, col = yx_to_rowcol[y, x] @@ -1139,72 +1139,72 @@ class Window(Container): cli, MouseEvent(position=Point(x=0, y=0), event_type=mouse_event.event_type)) - # If it returns NotImplemented, handle it here. - if result == NotImplemented: - return self._mouse_handler(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) - + # 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 UIContent object. - # already rendered those tokens using this size.) + # already rendered those tokens using this size.) return TokenListControl.static(tokens).create_content( cli, width + 1, write_position.height) - - 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. + + 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(cli, 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. + 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(cli, margin_screen, screen, write_position, move_x, width) - move_x += width - + move_x += width + def _copy_body(self, cli, ui_content, new_screen, write_position, move_x, width, vertical_scroll=0, horizontal_scroll=0, has_focus=False, wrap_lines=False, highlight_lines=False, vertical_scroll_2=0, always_hide_cursor=False): - """ + """ Copy the UIContent into the output screen. - """ - xpos = write_position.xpos + move_x - ypos = write_position.ypos + """ + xpos = write_position.xpos + move_x + ypos = write_position.ypos line_count = ui_content.line_count - new_buffer = new_screen.data_buffer + new_buffer = new_screen.data_buffer empty_char = _CHAR_CACHE['', Token] ZeroWidthEscape = Token.ZeroWidthEscape - + # Map visible line number to (row, col) of input. # 'col' will always be zero if line wrapping is off. visible_line_to_row_col = {} rowcol_to_yx = {} # Maps (row, col) from the input to (y, x) screen coordinates. - + # Fill background with default_char first. default_char = ui_content.default_char - + if default_char: for y in range(ypos, ypos + write_position.height): new_buffer_row = new_buffer[y] @@ -1295,33 +1295,33 @@ class Window(Container): # 'Invalid position. row=%r col=%r, vertical_scroll=%r, ' # 'horizontal_scroll=%r, height=%r' % # (row, col, vertical_scroll, horizontal_scroll, write_position.height)) - else: + else: return Point(y=y, x=x) - + # Set cursor and menu positions. if ui_content.cursor_position: screen_cursor_position = cursor_pos_to_screen_pos( ui_content.cursor_position.y, ui_content.cursor_position.x) - + if has_focus: new_screen.cursor_position = screen_cursor_position - + if always_hide_cursor: new_screen.show_cursor = False else: new_screen.show_cursor = ui_content.show_cursor - + self._highlight_digraph(cli, new_screen) - + if highlight_lines: self._highlight_cursorlines( cli, new_screen, screen_cursor_position, xpos, ypos, width, write_position.height) - + # Draw input characters from the input processor queue. if has_focus and ui_content.cursor_position: self._show_input_processor_key_buffer(cli, new_screen) - + # Set menu position. if not new_screen.menu_position and ui_content.menu_position: new_screen.menu_position = cursor_pos_to_screen_pos( @@ -1333,7 +1333,7 @@ class Window(Container): return visible_line_to_row_col, rowcol_to_yx def _highlight_digraph(self, cli, new_screen): - """ + """ When we are in Vi digraph mode, put a question mark underneath the cursor. """ @@ -1403,24 +1403,24 @@ class Window(Container): def _copy_margin(self, cli, lazy_screen, new_screen, write_position, move_x, width): """ - Copy characters from the margin screen to the real screen. - """ - xpos = write_position.xpos + move_x - ypos = write_position.ypos - + Copy characters from the margin screen to the real screen. + """ + xpos = write_position.xpos + move_x + ypos = write_position.ypos + margin_write_position = WritePosition(xpos, ypos, width, write_position.height) self._copy_body(cli, lazy_screen, new_screen, margin_write_position, 0, width) - + def _scroll_when_linewrapping(self, ui_content, width, height, cli): """ Scroll to make sure the cursor position is visible and that we maintain the requested scroll offset. - + Set `self.horizontal_scroll/vertical_scroll`. """ scroll_offsets_bottom = self.scroll_offsets.bottom scroll_offsets_top = self.scroll_offsets.top - + # We don't have horizontal scrolling. self.horizontal_scroll = 0 @@ -1506,12 +1506,12 @@ class Window(Container): self.vertical_scroll = min(self.vertical_scroll, topmost_visible) def _scroll_without_linewrapping(self, ui_content, width, height, cli): - """ + """ Scroll to make sure the cursor position is visible and that we maintain the requested scroll offset. Set `self.horizontal_scroll/vertical_scroll`. - """ + """ cursor_position = ui_content.cursor_position or Point(0, 0) # Without line wrapping, we will never have to scroll vertically inside @@ -1525,141 +1525,141 @@ class Window(Container): else: current_line_text = token_list_to_text(ui_content.get_line(cursor_position.y)) - def do_scroll(current_scroll, scroll_offset_start, scroll_offset_end, - cursor_pos, window_size, content_size): - " Scrolling algorithm. Used for both horizontal and vertical scrolling. " - # Calculate the scroll offset to apply. - # This can obviously never be more than have the screen size. Also, when the - # cursor appears at the top or bottom, we don't apply the offset. - scroll_offset_start = int(min(scroll_offset_start, window_size / 2, cursor_pos)) - scroll_offset_end = int(min(scroll_offset_end, window_size / 2, - content_size - 1 - cursor_pos)) - - # Prevent negative scroll offsets. - if current_scroll < 0: - current_scroll = 0 - - # Scroll back if we scrolled to much and there's still space to show more of the document. - if (not self.allow_scroll_beyond_bottom(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 - + 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 + return current_scroll - - # When a preferred scroll is given, take that first into account. - if self.get_vertical_scroll: - self.vertical_scroll = self.get_vertical_scroll(self) - assert isinstance(self.vertical_scroll, int) - if self.get_horizontal_scroll: - self.horizontal_scroll = self.get_horizontal_scroll(self) - assert isinstance(self.horizontal_scroll, int) - - # Update horizontal/vertical scroll to make sure that the cursor - # remains visible. - offsets = self.scroll_offsets - + + # When a preferred scroll is given, take that first into account. + if self.get_vertical_scroll: + self.vertical_scroll = self.get_vertical_scroll(self) + assert isinstance(self.vertical_scroll, int) + if self.get_horizontal_scroll: + self.horizontal_scroll = self.get_horizontal_scroll(self) + assert isinstance(self.horizontal_scroll, int) + + # Update horizontal/vertical scroll to make sure that the cursor + # remains visible. + offsets = self.scroll_offsets + self.vertical_scroll = do_scroll( - current_scroll=self.vertical_scroll, - scroll_offset_start=offsets.top, - scroll_offset_end=offsets.bottom, + current_scroll=self.vertical_scroll, + scroll_offset_start=offsets.top, + scroll_offset_end=offsets.bottom, cursor_pos=ui_content.cursor_position.y, - window_size=height, + window_size=height, content_size=ui_content.line_count) - + self.horizontal_scroll = do_scroll( - current_scroll=self.horizontal_scroll, - scroll_offset_start=offsets.left, - scroll_offset_end=offsets.right, + current_scroll=self.horizontal_scroll, + scroll_offset_start=offsets.left, + scroll_offset_end=offsets.right, cursor_pos=get_cwidth(current_line_text[:ui_content.cursor_position.x]), - window_size=width, + window_size=width, # We can only analyse the current line. Calculating the width off # all the lines is too expensive. content_size=max(get_cwidth(current_line_text), self.horizontal_scroll + width)) - - def _mouse_handler(self, cli, mouse_event): - """ - Mouse handler. Called when the UI control doesn't handle this - particular event. - """ + + 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 == MouseEventType.SCROLL_DOWN: - self._scroll_down(cli) + self._scroll_down(cli) elif mouse_event.event_type == MouseEventType.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: + 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: # TODO: not entirely correct yet in case of line wrapping and long lines. - if info.cursor_position.y >= info.window_height - 1 - info.configured_scroll_offsets.bottom: - self.content.move_cursor_up(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) - + 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 __repr__(self): return 'ConditionalContainer(%r, filter=%r)' % (self.content, self.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 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, max_available_height): - if self.filter(cli): + if self.filter(cli): return self.content.preferred_height(cli, width, max_available_height) - 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 + 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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/controls.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/controls.py index ca74931dbc..59107c7e5e 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/controls.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/controls.py @@ -1,99 +1,99 @@ -""" -User interface Controls for the layout. -""" -from __future__ import unicode_literals - -from abc import ABCMeta, abstractmethod +""" +User interface Controls for the layout. +""" +from __future__ import unicode_literals + +from abc import ABCMeta, abstractmethod from collections import namedtuple -from six import with_metaclass +from six import with_metaclass from six.moves import range - + from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER -from prompt_toolkit.filters import to_cli_filter +from prompt_toolkit.enums import DEFAULT_BUFFER, SEARCH_BUFFER +from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.mouse_events import MouseEventType -from prompt_toolkit.search_state import SearchState -from prompt_toolkit.selection import SelectionType +from prompt_toolkit.search_state import SearchState +from prompt_toolkit.selection import SelectionType from prompt_toolkit.token import Token from prompt_toolkit.utils import get_cwidth - -from .lexers import Lexer, SimpleLexer + +from .lexers import Lexer, SimpleLexer from .processors import Processor from .screen import Char, Point from .utils import token_list_width, split_lines, token_list_to_text - + import six -import time - - -__all__ = ( - 'BufferControl', - 'FillControl', - 'TokenListControl', - 'UIControl', +import time + + +__all__ = ( + 'BufferControl', + 'FillControl', + 'TokenListControl', + 'UIControl', 'UIContent', -) - - -class UIControl(with_metaclass(ABCMeta, object)): - """ - Base class for all user interface controls. - """ - def reset(self): - # Default reset. (Doesn't have to be implemented.) - pass - - def preferred_width(self, cli, max_available_width): - return None - +) + + +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, max_available_height, wrap_lines): - 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 + 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 by :meth:`.UIControl.create_content`. If the created content has the property ``show_cursor=False``, the cursor will be hidden from the output. - """ - return False - - @abstractmethod + """ + return False + + @abstractmethod def create_content(self, cli, width, height): - """ + """ Generate the content for this user control. - + Returns a :class:`.UIContent` instance. - """ - - 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. - """ - - + """ + + 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 UIContent(object): """ Content generated by a user control. This content consists of a list of @@ -166,135 +166,135 @@ class UIContent(object): return max(1, quotient) -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. - """ - def __init__(self, get_tokens, default_char=None, get_default_char=None, +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. + """ + def __init__(self, get_tokens, default_char=None, get_default_char=None, align_right=False, align_center=False, has_focus=False): assert callable(get_tokens) - 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.get_tokens = get_tokens - - # Construct `get_default_char` callable. - if default_char: - get_default_char = lambda _: default_char - elif not get_default_char: + 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.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.Transparent) - - self.get_default_char = get_default_char - + + self.get_default_char = get_default_char + #: Cache for the content. self._content_cache = SimpleCache(maxsize=18) self._token_cache = SimpleCache(maxsize=1) - # Only cache one token list. We don't need the previous item. - - # Render info for the mouse support. + # Only cache one token list. We don't need the previous item. + + # Render info for the mouse support. self._tokens = None - + def reset(self): self._tokens = None - 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.) - """ + 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_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. - """ + 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 = token_list_to_text(self._get_tokens_cached(cli)) - line_lengths = [get_cwidth(l) for l in text.split('\n')] - return max(line_lengths) - + line_lengths = [get_cwidth(l) for l in text.split('\n')] + return max(line_lengths) + def preferred_height(self, cli, width, max_available_height, wrap_lines): content = self.create_content(cli, width, None) return content.line_count - + def create_content(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. - 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) + # Get tokens + tokens_with_mouse_handlers = self._get_tokens_cached(cli) + + default_char = self.get_default_char(cli) + + # Wrap/align right/center parameters. + 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 - - if right or center: + + if right or center: token_lines_with_mouse_handlers = [] - for line in split_lines(tokens_with_mouse_handlers): + for line in split_lines(tokens_with_mouse_handlers): token_lines_with_mouse_handlers.append(process_line(line)) else: token_lines_with_mouse_handlers = list(split_lines(tokens_with_mouse_handlers)) - - # Strip mouse handlers from tokens. + + # Strip mouse handlers from tokens. token_lines = [ [tuple(item[:2]) for item in line] for line in token_lines_with_mouse_handlers ] - + # Keep track of the tokens with mouse handler, for later use in # `mouse_handler`. - self._tokens = tokens_with_mouse_handlers - + self._tokens = tokens_with_mouse_handlers + # If there is a `Token.SetCursorPosition` in the token list, set the # cursor position here. def get_cursor_position(): SetCursorPosition = Token.SetCursorPosition - + for y, line in enumerate(token_lines): x = 0 for token, text in line: @@ -302,38 +302,38 @@ class TokenListControl(UIControl): return Point(x=x, y=y) x += len(text) return None - + # Create content, or take it from the cache. key = (default_char.char, default_char.token, tuple(tokens_with_mouse_handlers), width, right, center) - + def get_content(): return UIContent(get_line=lambda i: token_lines[i], line_count=len(token_lines), default_char=default_char, cursor_position=get_cursor_position()) - + return self._content_cache.get(key, get_content) - @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.) - """ + @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._tokens: # Read the generator. tokens_for_line = list(split_lines(self._tokens)) - + try: tokens = tokens_for_line[mouse_event.position.y] except IndexError: @@ -342,38 +342,38 @@ class TokenListControl(UIControl): # Find position in the token list. xpos = mouse_event.position.x - # Find mouse handler for this character. - count = 0 + # Find mouse handler for this character. + count = 0 for item in tokens: - count += len(item[1]) + count += len(item[1]) if count >= xpos: - if len(item) >= 3: - # Handler found. Call it. + if len(item) >= 3: + # Handler found. Call it. # (Handler can return NotImplemented, so return # that result.) - handler = item[2] + handler = item[2] return handler(cli, mouse_event) - else: - break - - # Otherwise, don't handle here. - return NotImplemented - - -class FillControl(UIControl): - """ - Fill whole control with characters with this token. - (Also helpful for debugging.) + else: + break + + # Otherwise, don't handle here. + return NotImplemented + + +class FillControl(UIControl): + """ + Fill whole control with characters with this token. + (Also helpful for debugging.) :param char: :class:`.Char` instance to use for filling. :param get_char: A callable that takes a CommandLineInterface and returns a :class:`.Char` object. - """ + """ def __init__(self, character=None, token=Token, char=None, get_char=None): # 'character' and 'token' parameters are deprecated. assert char is None or isinstance(char, Char) assert get_char is None or callable(get_char) assert not (char and get_char) - + self.char = char if character: @@ -391,110 +391,110 @@ class FillControl(UIControl): self.get_char = lambda cli: self.char self.char = char - def __repr__(self): + def __repr__(self): if self.char: return '%s(char=%r)' % (self.__class__.__name__, self.char) else: return '%s(get_char=%r)' % (self.__class__.__name__, self.get_char) - - def reset(self): - pass - - def has_focus(self, cli): - return False - + + def reset(self): + pass + + def has_focus(self, cli): + return False + def create_content(self, cli, width, height): def get_line(i): return [] - + return UIContent( get_line=get_line, line_count=100 ** 100, # Something very big. default_char=self.get_char(cli)) - + _ProcessedLine = namedtuple('_ProcessedLine', 'tokens source_to_display display_to_source') -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 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, - lexer=None, - preview_search=False, - search_buffer_name=SEARCH_BUFFER, - get_search_state=None, - 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 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) +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 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, + lexer=None, + preview_search=False, + search_buffer_name=SEARCH_BUFFER, + get_search_state=None, + 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 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) assert default_char is None or isinstance(default_char, Char) - - self.preview_search = to_cli_filter(preview_search) - self.get_search_state = get_search_state - self.focus_on_click = to_cli_filter(focus_on_click) - - self.input_processors = input_processors 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 - + + self.preview_search = to_cli_filter(preview_search) + self.get_search_state = get_search_state + self.focus_on_click = to_cli_filter(focus_on_click) + + self.input_processors = input_processors 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 + #: 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. + #: 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_cache = SimpleCache(maxsize=8) - - self._xy_to_cursor_position = None - self._last_click_timestamp = None + + self._xy_to_cursor_position = None + self._last_click_timestamp = None self._last_get_processed_line = 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): + + 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): """ This should return the preferred width. - + Note: We don't specify a preferred width according to the content, because it would be too expensive. Calculating the preferred width can be done by calculating the longest line, but this would require applying all the processors to each line. This is unfeasible for a larger document, and doing it for small documents only would result in inconsistent behaviour. - """ + """ return None - + def preferred_height(self, cli, width, max_available_height, wrap_lines): # Calculate the content height, if it was drawn on a screen with the # given width. @@ -520,15 +520,15 @@ class BufferControl(UIControl): return height def _get_tokens_for_line_func(self, cli, document): - """ + """ Create a function that returns the tokens for a given line. """ # Cache using `document.text`. def get_tokens_for_line(): return self.lexer.lex_document(cli, document) - + return self._token_cache.get(document.text, get_tokens_for_line) - + def _create_get_processed_line_func(self, cli, document): """ Create a function that takes a line number of the current document and @@ -537,15 +537,15 @@ class BufferControl(UIControl): """ def transform(lineno, tokens): " Transform the tokens for a given line number. " - source_to_display_functions = [] - display_to_source_functions = [] - + source_to_display_functions = [] + display_to_source_functions = [] + # Get cursor position at this line. if document.cursor_position_row == lineno: cursor_column = document.cursor_position_col else: cursor_column = None - + def source_to_display(i): """ Translate x position from the buffer to the x position in the processed token list. """ @@ -554,28 +554,28 @@ class BufferControl(UIControl): return i # Apply each processor. - for p in self.input_processors: + for p in self.input_processors: transformation = p.apply_transformation( cli, document, lineno, source_to_display, tokens) - tokens = transformation.tokens - + tokens = transformation.tokens + if cursor_column: cursor_column = transformation.source_to_display(cursor_column) - + display_to_source_functions.append(transformation.display_to_source) source_to_display_functions.append(transformation.source_to_display) - + def display_to_source(i): - for f in reversed(display_to_source_functions): + for f in reversed(display_to_source_functions): i = f(i) return i - + return _ProcessedLine(tokens, source_to_display, display_to_source) - + def create_func(): get_line = self._get_tokens_for_line_func(cli, document) cache = {} - + def get_processed_line(i): try: return cache[i] @@ -584,48 +584,48 @@ class BufferControl(UIControl): cache[i] = processed_line return processed_line return get_processed_line - + return create_func() - + def create_content(self, cli, width, height): """ Create a UIContent. """ - 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 - + 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 + get_processed_line = self._create_get_processed_line_func(cli, document) self._last_get_processed_line = get_processed_line - + def translate_rowcol(row, col): " Return the content column for this coordinate. " return Point(y=row, x=get_processed_line(row).source_to_display(col)) - + def get_line(i): " Return the tokens for a given line number. " tokens = get_processed_line(i).tokens - + # Add a space at the end, because that is a possible cursor # position. (When inserting after the input.) We should do this on # all the lines, not just the line containing the cursor. (Because @@ -633,60 +633,60 @@ class BufferControl(UIControl): # cursor around.) tokens = tokens + [(self.default_char.token, ' ')] return tokens - + content = UIContent( get_line=get_line, line_count=document.line_count, cursor_position=translate_rowcol(document.cursor_position_row, document.cursor_position_col), default_char=self.default_char) - - # 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) + + # 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) menu_row, menu_col = buffer.document.translate_index_to_position(menu_position) content.menu_position = translate_rowcol(menu_row, menu_col) - elif buffer.complete_state: - # Position for completion menu. - # Note: We use 'min', because the original cursor position could be - # behind the input string when the actual completion is for - # some reason shorter than the text we had before. (A completion - # can change and shorten the input.) + elif buffer.complete_state: + # Position for completion menu. + # Note: We use 'min', because the original cursor position could be + # behind the input string when the actual completion is for + # some reason shorter than the text we had before. (A completion + # can change and shorten the input.) menu_row, menu_col = buffer.document.translate_index_to_position( - min(buffer.cursor_position, - buffer.complete_state.original_document.cursor_position)) + min(buffer.cursor_position, + buffer.complete_state.original_document.cursor_position)) content.menu_position = translate_rowcol(menu_row, menu_col) - else: + else: content.menu_position = None - + return content - - 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): + + 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._last_get_processed_line: processed_line = self._last_get_processed_line(position.y) - # Translate coordinates back to the cursor position of the - # original input. + # Translate coordinates back to the cursor position of the + # original input. xpos = processed_line.display_to_source(position.x) index = buffer.document.translate_row_col_to_index(position.y, xpos) - - # Set the cursor position. + + # Set the cursor position. if mouse_event.event_type == MouseEventType.MOUSE_DOWN: buffer.exit_selection() buffer.cursor_position = index - + elif mouse_event.event_type == MouseEventType.MOUSE_UP: # When the cursor was moved to another place, select the text. # (The >1 is actually a small but acceptable workaround for @@ -696,12 +696,12 @@ class BufferControl(UIControl): if abs(buffer.cursor_position - index) > 1: buffer.start_selection(selection_type=SelectionType.CHARACTERS) buffer.cursor_position = index - + # Select word around cursor on double click. # Two MOUSE_UP events in a short timespan are considered a double click. double_click = self._last_click_timestamp and time.time() - self._last_click_timestamp < .3 self._last_click_timestamp = time.time() - + if double_click: start, end = buffer.document.find_boundaries_of_current_word() buffer.cursor_position += start @@ -710,21 +710,21 @@ class BufferControl(UIControl): else: # Don't handle scroll events here. return NotImplemented - - # Not focussed, but focussing on click events. - else: + + # Not focussed, but focussing on click events. + else: if self.focus_on_click(cli) and mouse_event.event_type == MouseEventType.MOUSE_UP: - # Focus happens on mouseup. (If we did this on mousedown, the - # up event will be received at the point where this widget is - # 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() + # 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() diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/dimension.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/dimension.py index 717ad7a81f..82d2bb82f1 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/dimension.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/dimension.py @@ -1,92 +1,92 @@ -""" -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) +""" +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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/lexers.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/lexers.py index a928fd8226..8f3f36e2e2 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/lexers.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/lexers.py @@ -1,12 +1,12 @@ -""" -Lexer interface and implementation. -Used for syntax highlighting. -""" -from __future__ import unicode_literals -from abc import ABCMeta, abstractmethod -from six import with_metaclass +""" +Lexer interface and implementation. +Used for syntax highlighting. +""" +from __future__ import unicode_literals +from abc import ABCMeta, abstractmethod +from six import with_metaclass from six.moves import range - + from prompt_toolkit.token import Token from prompt_toolkit.filters import to_cli_filter from .utils import split_lines @@ -14,44 +14,44 @@ from .utils import split_lines import re import six -__all__ = ( - 'Lexer', - 'SimpleLexer', - 'PygmentsLexer', +__all__ = ( + 'Lexer', + 'SimpleLexer', + 'PygmentsLexer', 'SyntaxSync', 'SyncFromStart', 'RegexSync', -) - - -class Lexer(with_metaclass(ABCMeta, object)): - """ - Base class for all lexers. - """ - @abstractmethod +) + + +class Lexer(with_metaclass(ABCMeta, object)): + """ + Base class for all lexers. + """ + @abstractmethod def lex_document(self, cli, document): - """ + """ Takes a :class:`~prompt_toolkit.document.Document` and returns a callable that takes a line number and returns the tokens for that line. - """ - - -class SimpleLexer(Lexer): - """ + """ + + +class SimpleLexer(Lexer): + """ Lexer that doesn't do any tokenizing and returns the whole input as one token. :param token: The `Token` for this lexer. - """ + """ # `default_token` parameter is deprecated! def __init__(self, token=Token, default_token=None): self.token = token - + if default_token is not None: self.token = default_token - + def lex_document(self, cli, document): lines = document.lines - + def get_line(lineno): " Return the tokens for the given line. " try: @@ -146,9 +146,9 @@ class RegexSync(SyntaxSync): return cls(p) -class PygmentsLexer(Lexer): - """ - Lexer that calls a pygments lexer. +class PygmentsLexer(Lexer): + """ + Lexer that calls a pygments lexer. Example:: @@ -169,7 +169,7 @@ class PygmentsLexer(Lexer): recommended to disable this for inputs that are expected to be more than 1,000 lines. :param syntax_sync: `SyntaxSync` object. - """ + """ # Minimum amount of lines to go backwards when starting the parser. # This is important when the lines are retrieved in reverse order, or when # scrolling upwards. (Due to the complexity of calculating the vertical @@ -185,15 +185,15 @@ class PygmentsLexer(Lexer): def __init__(self, pygments_lexer_cls, sync_from_start=True, syntax_sync=None): assert syntax_sync is None or isinstance(syntax_sync, SyntaxSync) - self.pygments_lexer_cls = pygments_lexer_cls + self.pygments_lexer_cls = pygments_lexer_cls self.sync_from_start = to_cli_filter(sync_from_start) - - # Instantiate the Pygments lexer. - self.pygments_lexer = pygments_lexer_cls( - stripnl=False, - stripall=False, - ensurenl=False) - + + # Instantiate the Pygments lexer. + self.pygments_lexer = pygments_lexer_cls( + stripnl=False, + stripall=False, + ensurenl=False) + # Create syntax sync instance. self.syntax_sync = syntax_sync or RegexSync.from_pygments_lexer_cls(pygments_lexer_cls) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/margins.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/margins.py index 2934dfc9a7..5b3e116f65 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/margins.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/margins.py @@ -1,166 +1,166 @@ -""" -Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. -""" -from __future__ import unicode_literals - +""" +Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`. +""" +from __future__ import unicode_literals + from abc import ABCMeta, abstractmethod -from six import with_metaclass +from six import with_metaclass from six.moves import range - -from prompt_toolkit.filters import to_cli_filter + +from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.token import Token from prompt_toolkit.utils import get_cwidth from .utils import token_list_to_text - -__all__ = ( - 'Margin', - 'NumberredMargin', - 'ScrollbarMargin', - 'ConditionalMargin', + +__all__ = ( + 'Margin', + 'NumberredMargin', + 'ScrollbarMargin', + 'ConditionalMargin', 'PromptMargin', -) - - -class Margin(with_metaclass(ABCMeta, object)): - """ - Base interface for a margin. - """ - @abstractmethod +) + + +class Margin(with_metaclass(ABCMeta, object)): + """ + Base interface for a margin. + """ + @abstractmethod def get_width(self, cli, get_ui_content): - """ - Return the width that this margin is going to consume. + """ + Return the width that this margin is going to consume. :param cli: :class:`.CommandLineInterface` instance. :param get_ui_content: Callable that asks the user control to create a :class:`.UIContent` instance. This can be used for instance to obtain the number of lines. - """ - return 0 - - @abstractmethod - def create_margin(self, cli, window_render_info, width, height): - """ - Creates a margin. - This should return a list of (Token, text) tuples. - + """ + 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 cli: :class:`.CommandLineInterface` instance. - :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 relative: Number relative to the cursor position. Similar to the Vi - 'relativenumber' option. + :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 relative: Number relative to the cursor position. Similar to the Vi + 'relativenumber' option. :param display_tildes: Display tildes after the end of the document, just like Vi does. - """ + """ def __init__(self, relative=False, display_tildes=False): - self.relative = to_cli_filter(relative) + self.relative = to_cli_filter(relative) self.display_tildes = to_cli_filter(display_tildes) - + def get_width(self, cli, get_ui_content): line_count = get_ui_content().line_count return max(3, len('%s' % line_count) + 1) - - def create_margin(self, cli, window_render_info, width, height): - relative = self.relative(cli) - - token = Token.LineNumber - token_current = Token.LineNumber.Current - - # Get current line number. + + def create_margin(self, cli, window_render_info, width, height): + relative = self.relative(cli) + + token = Token.LineNumber + token_current = Token.LineNumber.Current + + # Get current line number. current_lineno = window_render_info.ui_content.cursor_position.y - - # Construct margin. - result = [] + + # Construct margin. + result = [] last_lineno = None - + for y, lineno in enumerate(window_render_info.displayed_lines): # Only display line number if this line is not a continuation of the previous line. if lineno != last_lineno: if lineno is None: pass elif lineno == current_lineno: - # Current line. - if relative: - # Left align current number in relative mode. + # Current line. + if relative: + # Left align current number in relative mode. result.append((token_current, '%i' % (lineno + 1))) - else: + else: result.append((token_current, ('%i ' % (lineno + 1)).rjust(width))) - else: - # Other lines. - if relative: + else: + # Other lines. + if relative: lineno = abs(lineno - current_lineno) - 1 - + result.append((token, ('%i ' % (lineno + 1)).rjust(width))) - + last_lineno = lineno - result.append((Token, '\n')) - + result.append((Token, '\n')) + # Fill with tildes. if self.display_tildes(cli): while y < window_render_info.window_height: result.append((Token.Tilde, '~\n')) y += 1 - return result - - -class ConditionalMargin(Margin): - """ - Wrapper around other :class:`.Margin` classes to show/hide them. - """ - def __init__(self, margin, filter): - assert isinstance(margin, Margin) - - self.margin = margin - self.filter = to_cli_filter(filter) - + 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, ui_content): - if self.filter(cli): + if self.filter(cli): return self.margin.get_width(cli, ui_content) - 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. + 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. :param display_arrows: Display scroll up/down arrows. - """ + """ def __init__(self, display_arrows=False): self.display_arrows = to_cli_filter(display_arrows) def get_width(self, cli, ui_content): - return 1 - - def create_margin(self, cli, window_render_info, width, height): - total_height = window_render_info.content_height + return 1 + + def create_margin(self, cli, window_render_info, width, height): + total_height = window_render_info.content_height display_arrows = self.display_arrows(cli) - + window_height = window_render_info.window_height if display_arrows: window_height -= 2 - + try: items_per_row = float(total_height) / min(total_height, window_height) except ZeroDivisionError: @@ -170,7 +170,7 @@ class ScrollbarMargin(Margin): " True if we should display a button on this row. " current_row_middle = int((row + .5) * items_per_row) return current_row_middle in window_render_info.displayed_lines - + # Up arrow. result = [] if display_arrows: @@ -178,7 +178,7 @@ class ScrollbarMargin(Margin): (Token.Scrollbar.Arrow, '^'), (Token.Scrollbar, '\n') ]) - + # Scrollbar body. for i in range(window_height): if is_scroll_button(i): @@ -186,7 +186,7 @@ class ScrollbarMargin(Margin): else: result.append((Token.Scrollbar, ' ')) result.append((Token, '\n')) - + # Down arrow if display_arrows: result.append((Token.Scrollbar.Arrow, 'v')) @@ -246,8 +246,8 @@ class PromptMargin(Margin): if show_numbers: if y != last_y: tokens.append((Token.LineNumber, ('%i ' % (y + 1)).rjust(width))) - else: + else: tokens.extend(tokens2) last_y = y - + return tokens diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/menus.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/menus.py index a916846e45..01e2a8358b 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/menus.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/menus.py @@ -1,496 +1,496 @@ -from __future__ import unicode_literals - +from __future__ import unicode_literals + from six.moves import zip_longest, range from prompt_toolkit.filters import HasCompletions, IsDone, Condition, to_cli_filter from prompt_toolkit.mouse_events import MouseEventType from prompt_toolkit.token import Token -from prompt_toolkit.utils import get_cwidth - +from prompt_toolkit.utils import get_cwidth + from .containers import Window, HSplit, ConditionalContainer, ScrollOffsets from .controls import UIControl, UIContent -from .dimension import LayoutDimension +from .dimension import LayoutDimension from .margins import ScrollbarMargin from .screen import Point, Char - -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. - """ + +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. + """ # Preferred minimum size of the menu control. # The CompletionsMenu class defines a width of 8, and there is a scrollbar # of 1.) MIN_WIDTH = 7 - + def __init__(self): - self.token = Token.Menu.Completions - - 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) - + self.token = Token.Menu.Completions + + 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 - else: - return 0 - + else: + return 0 + def preferred_height(self, cli, width, max_available_height, wrap_lines): - complete_state = cli.current_buffer.complete_state - if complete_state: - return len(complete_state.current_completions) - else: - return 0 - + complete_state = cli.current_buffer.complete_state + if complete_state: + return len(complete_state.current_completions) + else: + return 0 + def create_content(self, cli, width, height): - """ + """ Create a UIContent object for this control. - """ - 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. + """ + 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, complete_state) menu_meta_width = self._get_menu_meta_width(width - menu_width, complete_state) - show_meta = self._show_meta(complete_state) - + show_meta = self._show_meta(complete_state) + def get_line(i): c = completions[i] is_current_completion = (i == index) result = self._get_menu_item_tokens(c, is_current_completion, menu_width) - + if show_meta: result += self._get_menu_item_meta_tokens(c, is_current_completion, menu_meta_width) return result - + return UIContent(get_line=get_line, cursor_position=Point(x=0, y=index or 0), line_count=len(completions), default_char=Char(' ', self.token)) - + return UIContent() - - def _show_meta(self, complete_state): - """ - Return ``True`` if we need to show a column with meta information. - """ - return any(c.display_meta for c in complete_state.current_completions) - - def _get_menu_width(self, max_width, complete_state): - """ - Return the width of the main column. - """ + + 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(self.MIN_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 - + + 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 == MouseEventType.MOUSE_UP: - # Select completion. + # Select completion. b.go_to_completion(mouse_event.position.y) - b.complete_state = None - + b.complete_state = None + elif mouse_event.event_type == MouseEventType.SCROLL_DOWN: - # Scroll up. - b.complete_next(count=3, disable_wrap_around=True) - + # Scroll up. + b.complete_next(count=3, disable_wrap_around=True) + elif mouse_event.event_type == MouseEventType.SCROLL_UP: - # Scroll down. - b.complete_previous(count=3, disable_wrap_around=True) - - -def _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): + # Scroll down. + b.complete_previous(count=3, disable_wrap_around=True) + + +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=True, display_arrows=False): extra_filter = to_cli_filter(extra_filter) display_arrows = to_cli_filter(display_arrows) - super(CompletionsMenu, self).__init__( - content=Window( + super(CompletionsMenu, self).__init__( + content=Window( content=CompletionsMenuControl(), - width=LayoutDimension(min=8), + width=LayoutDimension(min=8), height=LayoutDimension(min=1, max=max_height), scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset), right_margins=[ScrollbarMargin(display_arrows=display_arrows)], dont_extend_width=True, ), - # 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 - + # 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, max_available_height, wrap_lines): - """ - 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))) - + """ + 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_content(self, cli, width, height): - """ + """ Create a UIContent object for this menu. - """ - complete_state = cli.current_buffer.complete_state - column_width = self._get_column_width(complete_state) - self._render_pos_to_completion = {} - - def grouper(n, iterable, fillvalue=None): - " grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx " - args = [iter(iterable)] * n - return zip_longest(fillvalue=fillvalue, *args) - - def is_current_completion(completion): - " Returns True when this completion is the currently selected one. " - return complete_state.complete_index is not None and c == complete_state.current_completion - - # Space required outside of the regular columns, for displaying the - # left and right arrow. - HORIZONTAL_MARGIN_REQUIRED = 3 - - if complete_state: - # There should be at least one column, but it cannot be wider than - # the available width. - column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) - - # However, when the columns tend to be very wide, because there are - # some very wide entries, shrink it anyway. - if column_width > self.suggested_max_column_width: - # `column_width` can still be bigger that `suggested_max_column_width`, - # but if there is place for two columns, we divide by two. - column_width //= (column_width // self.suggested_max_column_width) - - visible_columns = max(1, (width - self._required_margin) // column_width) - - columns_ = list(grouper(height, complete_state.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. + """ + complete_state = cli.current_buffer.complete_state + column_width = self._get_column_width(complete_state) + self._render_pos_to_completion = {} + + def grouper(n, iterable, fillvalue=None): + " grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx " + args = [iter(iterable)] * n + return zip_longest(fillvalue=fillvalue, *args) + + def is_current_completion(completion): + " Returns True when this completion is the currently selected one. " + return complete_state.complete_index is not None and c == complete_state.current_completion + + # Space required outside of the regular columns, for displaying the + # left and right arrow. + HORIZONTAL_MARGIN_REQUIRED = 3 + + if complete_state: + # There should be at least one column, but it cannot be wider than + # the available width. + column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width) + + # However, when the columns tend to be very wide, because there are + # some very wide entries, shrink it anyway. + if column_width > self.suggested_max_column_width: + # `column_width` can still be bigger that `suggested_max_column_width`, + # but if there is place for two columns, we divide by two. + column_width //= (column_width // self.suggested_max_column_width) + + visible_columns = max(1, (width - self._required_margin) // column_width) + + columns_ = list(grouper(height, complete_state.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_line = [] - - for row_index, row in enumerate(rows_): + + for row_index, row in enumerate(rows_): tokens = [] - middle_row = row_index == len(rows_) // 2 - - # Draw left arrow if we have hidden completions on the left. - if render_left_arrow: + middle_row = row_index == len(rows_) // 2 + + # Draw left arrow if we have hidden completions on the left. + if render_left_arrow: tokens += [(Token.Scrollbar, '<' 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: + + # 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 += [(Token.Scrollbar, '>' if middle_row else ' ')] - - # Newline. + + # Newline. tokens_for_line.append(tokens) - + else: tokens = [] - - 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 - + + self._rendered_rows = height + self._rendered_columns = visible_columns + self._total_columns = len(columns_) + self._render_left_arrow = render_left_arrow + self._render_right_arrow = render_right_arrow + self._render_width = column_width * visible_columns + render_left_arrow + render_right_arrow + 1 + def get_line(i): return tokens_for_line[i] - + return UIContent(get_line=get_line, line_count=len(rows_)) - def _get_column_width(self, complete_state): - """ - Return the width of each column. - """ - return max(get_cwidth(c.display) 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) - + 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 == MouseEventType.SCROLL_DOWN: - scroll_right() - + scroll_right() + elif mouse_event.event_type == MouseEventType.SCROLL_UP: - scroll_left() - + scroll_left() + elif mouse_event.event_type == MouseEventType.MOUSE_UP: - x = mouse_event.position.x - y = mouse_event.position.y - - # Mouse click on left arrow. - if x == 0: - if self._render_left_arrow: - scroll_left() - - # Mouse click on right arrow. - elif x == self._render_width - 1: - if self._render_right_arrow: - scroll_right() - - # Mouse click on completion. - else: - completion = self._render_pos_to_completion.get((x, y)) - if completion: - b.apply_completion(completion) - - -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 - + 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, max_available_height, wrap_lines): - return 1 - + return 1 + def create_content(self, cli, width, height): tokens = self._get_tokens(cli) - + def get_line(i): return tokens return UIContent(get_line=get_line, line_count=1 if tokens else 0) - 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 [] + 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 [] diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/mouse_handlers.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/mouse_handlers.py index d443bf8315..57e4125b6d 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/mouse_handlers.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/mouse_handlers.py @@ -1,29 +1,29 @@ -from __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 +from __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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/processors.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/processors.py index 0b8bc9c223..6054fc3caf 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/processors.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/processors.py @@ -1,55 +1,55 @@ -""" -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 +""" +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 six.moves import range - + from prompt_toolkit.cache import SimpleCache -from prompt_toolkit.document import Document -from prompt_toolkit.enums import SEARCH_BUFFER +from prompt_toolkit.document import Document +from prompt_toolkit.enums import SEARCH_BUFFER from prompt_toolkit.filters import to_cli_filter, ViInsertMultipleMode -from prompt_toolkit.layout.utils import token_list_to_text +from prompt_toolkit.layout.utils import token_list_to_text from prompt_toolkit.reactive import Integer from prompt_toolkit.token import Token - + from .utils import token_list_len, explode_tokens - + import re -__all__ = ( - 'Processor', - 'Transformation', - - 'HighlightSearchProcessor', - 'HighlightSelectionProcessor', - 'PasswordProcessor', +__all__ = ( + 'Processor', + 'Transformation', + + 'HighlightSearchProcessor', + 'HighlightSelectionProcessor', + 'PasswordProcessor', 'HighlightMatchingBracketProcessor', 'DisplayMultipleCursors', - 'BeforeInput', - 'AfterInput', - 'AppendAutoSuggestion', - 'ConditionalProcessor', - 'ShowLeadingWhiteSpaceProcessor', - 'ShowTrailingWhiteSpaceProcessor', + 'BeforeInput', + 'AfterInput', + 'AppendAutoSuggestion', + 'ConditionalProcessor', + 'ShowLeadingWhiteSpaceProcessor', + 'ShowTrailingWhiteSpaceProcessor', 'TabsProcessor', -) - - -class Processor(with_metaclass(ABCMeta, object)): - """ +) + + +class Processor(with_metaclass(ABCMeta, object)): + """ Manipulate the tokens for a given line in a - :class:`~prompt_toolkit.layout.controls.BufferControl`. - """ - @abstractmethod + :class:`~prompt_toolkit.layout.controls.BufferControl`. + """ + @abstractmethod def apply_transformation(self, cli, document, lineno, source_to_display, tokens): - """ - Apply transformation. Returns a :class:`.Transformation` instance. + """ + Apply transformation. Returns a :class:`.Transformation` instance. :param cli: :class:`.CommandLineInterface` instance. :param lineno: The number of the line to which we apply the processor. @@ -58,114 +58,114 @@ class Processor(with_metaclass(ABCMeta, object)): previous processors into account.) :param tokens: List of tokens that we can transform. (Received from the previous processor.) - """ + """ return Transformation(tokens) - - def has_focus(self, cli): - """ - Processors can override the focus. - (Used for the reverse-i-search prefix in DefaultPrompt.) - """ - return False - - -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 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 has_focus(self, cli): + """ + Processors can override the focus. + (Used for the reverse-i-search prefix in DefaultPrompt.) + """ + return False + + +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 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, tokens, source_to_display=None, display_to_source=None): - 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) - - + 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): - """ - Processor that highlights search matches in the document. + """ + Processor that highlights search matches in the document. Note that this doesn't support multiline search matches yet. - - :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 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, get_search_state=None): - self.preview_search = to_cli_filter(preview_search) - self.search_buffer_name = search_buffer_name + self.preview_search = to_cli_filter(preview_search) + self.search_buffer_name = search_buffer_name self.get_search_state = get_search_state or (lambda cli: cli.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. - else: + + 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 self.get_search_state(cli).text - + def apply_transformation(self, cli, document, lineno, source_to_display, tokens): - search_text = self._get_search_text(cli) + search_text = self._get_search_text(cli) searchmatch_current_token = (':', ) + Token.SearchMatch.Current searchmatch_token = (':', ) + Token.SearchMatch - - if search_text and not cli.is_returning: - # For each search match, replace the Token. + + if search_text and not cli.is_returning: + # For each search match, replace the Token. line_text = token_list_to_text(tokens) tokens = explode_tokens(tokens) - + flags = re.IGNORECASE if cli.is_ignoring_case else 0 - + # Get cursor column. if document.cursor_position_row == lineno: cursor_column = source_to_display(document.cursor_position_col) else: cursor_column = None - + for match in re.finditer(re.escape(search_text), line_text, flags=flags): if cursor_column is not None: on_cursor = match.start() <= cursor_column < match.end() else: on_cursor = False - + for i in range(match.start(), match.end()): old_token, text = tokens[i] if on_cursor: tokens[i] = (old_token + searchmatch_current_token, tokens[i][1]) else: tokens[i] = (old_token + searchmatch_token, tokens[i][1]) - + return Transformation(tokens) - - + + class HighlightSelectionProcessor(Processor): - """ - Processor that highlights the selection in the document. - """ + """ + Processor that highlights the selection in the document. + """ def apply_transformation(self, cli, document, lineno, source_to_display, tokens): selected_token = (':', ) + Token.SelectedText - # In case of selection, highlight all matches. + # In case of selection, highlight all matches. selection_at_line = document.selection_range_at_line(lineno) - + if selection_at_line: from_, to = selection_at_line from_ = source_to_display(from_) to = source_to_display(to) - + tokens = explode_tokens(tokens) - + if from_ == 0 and to == 0 and len(tokens) == 0: # When this is an empty line, insert a space in order to # visualiase the selection. @@ -175,43 +175,43 @@ class HighlightSelectionProcessor(Processor): if i < len(tokens): old_token, old_text = tokens[i] tokens[i] = (old_token + selected_token, old_text) - + return Transformation(tokens) - - -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 - + + +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, lineno, source_to_display, tokens): tokens = [(token, self.char * len(text)) for token, text in tokens] return Transformation(tokens) - - + + class HighlightMatchingBracketProcessor(Processor): - """ - When the cursor is on or right after a bracket, it highlights the matching - bracket. + """ + When the cursor is on or right after a bracket, it highlights the matching + bracket. :param max_cursor_distance: Only highlight matching brackets when the cursor is within this distance. (From inside a `Processor`, we can't know which lines will be visible on the screen. But we also don't want to scan the whole document for matching brackets on each key press, so we limit to this value.) - """ - _closing_braces = '])}>' - + """ + _closing_braces = '])}>' + def __init__(self, chars='[](){}<>', max_cursor_distance=1000): - self.chars = chars + self.chars = chars self.max_cursor_distance = max_cursor_distance - + self._positions_cache = SimpleCache(maxsize=8) - + def _get_positions_to_highlight(self, document): """ Return a list of (row, col) tuples that need to be highlighted. @@ -221,18 +221,18 @@ class HighlightMatchingBracketProcessor(Processor): pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) - + # Try for the character before the cursor. elif (document.char_before_cursor and document.char_before_cursor in self._closing_braces and document.char_before_cursor in self.chars): document = Document(document.text, document.cursor_position - 1) - + pos = document.find_matching_bracket_position( start_pos=document.cursor_position - self.max_cursor_distance, end_pos=document.cursor_position + self.max_cursor_distance) else: pos = None - + # Return a list of (row, col) tuples that need to be highlighted. if pos: pos += document.cursor_position # pos is relative. @@ -240,13 +240,13 @@ class HighlightMatchingBracketProcessor(Processor): return [(row, col), (document.cursor_position_row, document.cursor_position_col)] else: return [] - + def apply_transformation(self, cli, document, lineno, source_to_display, tokens): # Get the highlight positions. key = (cli.render_counter, document.text, document.cursor_position) positions = self._positions_cache.get( key, lambda: self._get_positions_to_highlight(document)) - + # Apply if positions were found at this line. if positions: for row, col in positions: @@ -254,172 +254,172 @@ class HighlightMatchingBracketProcessor(Processor): col = source_to_display(col) tokens = explode_tokens(tokens) token, text = tokens[col] - + if col == document.cursor_position_col: token += (':', ) + Token.MatchingBracket.Cursor else: token += (':', ) + Token.MatchingBracket.Other - + tokens[col] = (token, text) - + return Transformation(tokens) class DisplayMultipleCursors(Processor): - """ + """ When we're in Vi block insert mode, display all the cursors. - """ + """ _insert_multiple = ViInsertMultipleMode() - + def __init__(self, buffer_name): self.buffer_name = buffer_name - + def apply_transformation(self, cli, document, lineno, source_to_display, tokens): buff = cli.buffers[self.buffer_name] - + if self._insert_multiple(cli): positions = buff.multiple_cursor_positions tokens = explode_tokens(tokens) - + # If any cursor appears on the current line, highlight that. start_pos = document.translate_row_col_to_index(lineno, 0) end_pos = start_pos + len(document.lines[lineno]) - + token_suffix = (':', ) + Token.MultipleCursors.Cursor - + for p in positions: if start_pos <= p < end_pos: column = source_to_display(p - start_pos) - + # Replace token. token, text = tokens[column] token += token_suffix tokens[column] = (token, text) elif p == end_pos: tokens.append((token_suffix, ' ')) - + return Transformation(tokens) else: return Transformation(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 - +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, lineno, source_to_display, tokens): if lineno == 0: tokens_before = self.get_tokens(cli) tokens = tokens_before + tokens - + shift_position = token_list_len(tokens_before) source_to_display = lambda i: i + shift_position display_to_source = lambda i: i - shift_position else: source_to_display = None display_to_source = None - + return Transformation(tokens, source_to_display=source_to_display, display_to_source=display_to_source) - @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) - - -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 - + @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) + + +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, lineno, source_to_display, tokens): # Insert tokens after the last line. if lineno == document.line_count - 1: return Transformation(tokens=tokens + self.get_tokens(cli)) else: return Transformation(tokens=tokens) - - @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) - - -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 - + + @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) + + +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, lineno, source_to_display, tokens): # Insert tokens after the last line. if lineno == document.line_count - 1: buffer = self._get_buffer(cli) - + if buffer.suggestion and buffer.document.is_cursor_at_the_end: suggestion = buffer.suggestion.text else: suggestion = '' return Transformation(tokens=tokens + [(self.token, suggestion)]) - else: + else: return Transformation(tokens=tokens) - - -class ShowLeadingWhiteSpaceProcessor(Processor): - """ - Make leading whitespace visible. + + +class ShowLeadingWhiteSpaceProcessor(Processor): + """ + Make leading whitespace visible. :param get_char: Callable that takes a :class:`CommandLineInterface` instance and returns one character. :param token: Token to be used. - """ + """ def __init__(self, get_char=None, token=Token.LeadingWhiteSpace): assert get_char is None or callable(get_char) @@ -430,32 +430,32 @@ class ShowLeadingWhiteSpaceProcessor(Processor): else: return '\xb7' - self.token = token + self.token = token self.get_char = get_char - + def apply_transformation(self, cli, document, lineno, source_to_display, tokens): - # Walk through all te tokens. + # Walk through all te tokens. if tokens and token_list_to_text(tokens).startswith(' '): t = (self.token, self.get_char(cli)) tokens = explode_tokens(tokens) - + for i in range(len(tokens)): if tokens[i][1] == ' ': tokens[i] = t else: break - + return Transformation(tokens) - - -class ShowTrailingWhiteSpaceProcessor(Processor): - """ - Make trailing whitespace visible. + + +class ShowTrailingWhiteSpaceProcessor(Processor): + """ + Make trailing whitespace visible. :param get_char: Callable that takes a :class:`CommandLineInterface` instance and returns one character. :param token: Token to be used. - """ + """ def __init__(self, get_char=None, token=Token.TrailingWhiteSpace): assert get_char is None or callable(get_char) @@ -466,10 +466,10 @@ class ShowTrailingWhiteSpaceProcessor(Processor): else: return '\xb7' - self.token = token + self.token = token self.get_char = get_char - - + + def apply_transformation(self, cli, document, lineno, source_to_display, tokens): if tokens and tokens[-1][1].endswith(' '): t = (self.token, self.get_char(cli)) @@ -535,16 +535,16 @@ class TabsProcessor(Processor): result_tokens.append((token, separator1)) result_tokens.append((token, separator2 * (count - 1))) pos += count - else: + else: result_tokens.append(token_and_text) pos += 1 - + position_mappings[len(tokens)] = pos - + def source_to_display(from_position): " Maps original cursor position to the new one. " return position_mappings[from_position] - + def display_to_source(display_pos): " Maps display cursor position to the original one. " position_mappings_reversed = dict((v, k) for k, v in position_mappings.items()) @@ -562,44 +562,44 @@ class TabsProcessor(Processor): display_to_source=display_to_source) -class ConditionalProcessor(Processor): - """ - Processor that applies another processor, according to a certain condition. - Example:: - - # Create a function that returns whether or not the processor should - # currently be applied. - def highlight_enabled(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) - +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, lineno, source_to_display, tokens): - # Run processor when enabled. - if self.filter(cli): + # Run processor when enabled. + if self.filter(cli): return self.processor.apply_transformation( cli, document, lineno, source_to_display, tokens) - else: + else: return Transformation(tokens) - - def has_focus(self, cli): - if self.filter(cli): - return self.processor.has_focus(cli) - else: - return False - - def __repr__(self): - return '%s(processor=%r, filter=%r)' % ( - self.__class__.__name__, self.processor, self.filter) + + def has_focus(self, cli): + if self.filter(cli): + return self.processor.has_focus(cli) + else: + return False + + def __repr__(self): + return '%s(processor=%r, filter=%r)' % ( + self.__class__.__name__, self.processor, self.filter) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/prompt.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/prompt.py index 7d00ec513e..d6c6a3c838 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/prompt.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/prompt.py @@ -1,111 +1,111 @@ -from __future__ import unicode_literals - -from six import text_type - -from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER +from __future__ import unicode_literals + +from six import text_type + +from prompt_toolkit.enums import IncrementalSearchDirection, SEARCH_BUFFER from prompt_toolkit.token import Token - -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) - + +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, lineno, source_to_display, 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) - + # 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) + # Only show the prompt before the first line. For the following lines, # only indent using spaces. if lineno != 0: before = [(Token.Prompt, ' ' * shift_position)] - return Transformation( - 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 _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, ') '), - ] + return Transformation( + 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 _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, ') '), + ] diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/screen.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/screen.py index 95561f5de7..8926f66d9b 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/screen.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/screen.py @@ -1,151 +1,151 @@ -from __future__ import unicode_literals - +from __future__ import unicode_literals + from prompt_toolkit.cache import FastDictCache from prompt_toolkit.token import Token -from prompt_toolkit.utils import get_cwidth - -from collections import defaultdict, namedtuple - -__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': '^_', +from prompt_toolkit.utils import get_cwidth + +from collections import defaultdict, namedtuple + +__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': '^?', # 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) - - + } + + 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) + + _CHAR_CACHE = FastDictCache(Char, size=1000 * 1000) -Transparent = Token.Transparent - - -class Screen(object): - """ - Two dimentional buffer of :class:`.Char` instances. - """ - def __init__(self, default_char=None, initial_width=0, initial_height=0): - if default_char is None: +Transparent = Token.Transparent + + +class Screen(object): + """ + Two dimentional buffer of :class:`.Char` instances. + """ + def __init__(self, default_char=None, initial_width=0, initial_height=0): + if default_char is None: default_char = _CHAR_CACHE[' ', Transparent] - - self.data_buffer = defaultdict(lambda: defaultdict(lambda: default_char)) - + + self.data_buffer = defaultdict(lambda: defaultdict(lambda: default_char)) + #: Escape sequences to be injected. self.zero_width_escapes = defaultdict(lambda: defaultdict(lambda: '')) - #: Position of the cursor. - self.cursor_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 - - 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) + #: 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 + + 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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/toolbars.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/toolbars.py index 2e77c2fa16..aafa2e6245 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/toolbars.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/toolbars.py @@ -1,209 +1,209 @@ -from __future__ import unicode_literals - -from ..enums import IncrementalSearchDirection - -from .processors import BeforeInput - +from __future__ import unicode_literals + +from ..enums import IncrementalSearchDirection + +from .processors import BeforeInput + from .lexers import SimpleLexer -from .dimension import LayoutDimension +from .dimension import LayoutDimension from .controls import BufferControl, TokenListControl, UIControl, UIContent -from .containers import Window, ConditionalContainer +from .containers import Window, ConditionalContainer from .screen import Char -from .utils import token_list_len +from .utils import token_list_len from prompt_toolkit.enums import SEARCH_BUFFER, SYSTEM_BUFFER -from prompt_toolkit.filters import HasFocus, HasArg, HasCompletions, HasValidationError, HasSearch, Always, IsDone +from prompt_toolkit.filters import HasFocus, HasArg, HasCompletions, HasValidationError, HasSearch, Always, IsDone from prompt_toolkit.token import Token - -__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): + +__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): token = Token.Toolbar.System - super(SystemToolbarControl, self).__init__( - buffer_name=SYSTEM_BUFFER, + super(SystemToolbarControl, self).__init__( + buffer_name=SYSTEM_BUFFER, default_char=Char(token=token), lexer=SimpleLexer(token=token.Text), input_processors=[BeforeInput.static('Shell command: ', token)],) - - -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): + + +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): arg = cli.input_processor.arg if arg == '-': arg = '-1' - return [ - (Token.Toolbar.Arg, 'Repeat: '), + return [ + (Token.Toolbar.Arg, 'Repeat: '), (Token.Toolbar.Arg.Text, 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)], + ] + + 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)], default_char=Char(token=token), lexer=SimpleLexer(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 - + + +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_content(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 = [] - + 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 = [] + def get_line(i): return all_tokens - + return UIContent(get_line=get_line, line_count=1) - - -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()) + + +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()) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/utils.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/utils.py index a4fb7ed0f5..5fdfe4eb43 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/utils.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/layout/utils.py @@ -1,112 +1,112 @@ -from __future__ import unicode_literals - -from prompt_toolkit.utils import get_cwidth +from __future__ import unicode_literals + +from prompt_toolkit.utils import get_cwidth from prompt_toolkit.token import Token - -__all__ = ( - 'token_list_len', - 'token_list_width', - 'token_list_to_text', - 'explode_tokens', + +__all__ = ( + 'token_list_len', + 'token_list_width', + 'token_list_to_text', + 'explode_tokens', 'split_lines', - '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. - """ + '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. + """ ZeroWidthEscape = Token.ZeroWidthEscape return sum(len(item[1]) for item in tokenlist if item[0] != ZeroWidthEscape) - - -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. - """ + + +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. + """ ZeroWidthEscape = Token.ZeroWidthEscape return sum(get_cwidth(c) for item in tokenlist for c in item[1] if item[0] != ZeroWidthEscape) - - -def token_list_to_text(tokenlist): - """ - Concatenate all the text parts again. - """ + + +def token_list_to_text(tokenlist): + """ + Concatenate all the text parts again. + """ ZeroWidthEscape = Token.ZeroWidthEscape return ''.join(item[1] for item in tokenlist if item[0] != ZeroWidthEscape) - - -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 + + +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. Just like str.split, this will yield at least one item. - - :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])) + + :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])) # Note that parts[-1] can be empty, and that's fine. It happens # in the case of [(Token.SetCursorPosition, '')]. - - # 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)) - + + # 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)) + # Always yield the last line, even when this is an empty line. This ensures # that when `tokenlist` ends with a newline character, an additional empty # line is yielded. (Otherwise, there's no way to differentiate between the # cases where `tokenlist` does and doesn't end with a newline.) yield line - - + + class _ExplodedList(list): """ Wrapper around a list, that marks it as 'exploded'. @@ -140,42 +140,42 @@ class _ExplodedList(list): super(_ExplodedList, self).__setitem__(index, value) -def explode_tokens(tokenlist): - """ - Turn a list of (token, text) tuples into another list where each string is - exactly one character. - +def explode_tokens(tokenlist): + """ + Turn a list of (token, text) tuples into another list where each string is + exactly one character. + It should be fine to call this function several times. Calling this on a list that is already exploded, is a null operation. - :param tokenlist: List of (token, text) tuples. - """ + :param tokenlist: List of (token, text) tuples. + """ # When the tokenlist is already exploded, don't explode again. if getattr(tokenlist, 'exploded', False): return tokenlist - result = [] - - for token, string in tokenlist: - for c in string: - result.append((token, c)) - + result = [] + + for token, string in tokenlist: + for c in string: + result.append((token, c)) + return _ExplodedList(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 + + +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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/mouse_events.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/mouse_events.py index f42276ce9f..d827470be2 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/mouse_events.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/mouse_events.py @@ -1,48 +1,48 @@ -""" -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__ = ( +""" +Mouse events. + + +How it works +------------ + +The renderer has a 2 dimensional grid of mouse event handlers. +(`prompt_toolkit.layout.MouseHandlers`.) When the layout is rendered, the +`Window` class will make sure that this grid will also be filled with +callbacks. For vt100 terminals, mouse events are received through stdin, just +like any other key press. There is a handler among the key bindings that +catches these events and forwards them to such a mouse event handler. It passes +through the `Window` class where the coordinates are translated from absolute +coordinates to coordinates relative to the user control, and there +`UIControl.mouse_handler` is called. +""" +from __future__ import unicode_literals + +__all__ = ( 'MouseEventType', - 'MouseEvent' -) - - + 'MouseEvent' +) + + class MouseEventType: - MOUSE_UP = 'MOUSE_UP' - MOUSE_DOWN = 'MOUSE_DOWN' - SCROLL_UP = 'SCROLL_UP' - SCROLL_DOWN = 'SCROLL_DOWN' - - + MOUSE_UP = 'MOUSE_UP' + MOUSE_DOWN = 'MOUSE_DOWN' + SCROLL_UP = 'SCROLL_UP' + SCROLL_DOWN = 'SCROLL_DOWN' + + MouseEventTypes = MouseEventType # Deprecated: plural for backwards compatibility. -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) +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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/output.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/output.py index 072fb0677f..0b06acac47 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/output.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/output.py @@ -1,26 +1,26 @@ -""" -Interface for an output. -""" -from __future__ import unicode_literals -from abc import ABCMeta, abstractmethod -from six import with_metaclass +""" +Interface for an output. +""" +from __future__ import unicode_literals +from abc import ABCMeta, abstractmethod +from six import with_metaclass from prompt_toolkit.layout.screen import Size - -__all__ = ( - 'Output', -) - - -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 + +__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 fileno(self): " Return the file descriptor to which we can write for the output. " @@ -34,119 +34,119 @@ class Output(with_metaclass(ABCMeta, object)): """ @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. " + 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. " class DummyOutput(Output): diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/reactive.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/reactive.py index ec3aa06712..142b3e0b14 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/reactive.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/reactive.py @@ -1,56 +1,56 @@ -""" -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 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()) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/renderer.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/renderer.py index 7a8fde55b3..5c8b2b18d1 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/renderer.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/renderer.py @@ -1,118 +1,118 @@ -""" -Renders the command line on the console. -(Redraws parts of the input line that were changed.) -""" -from __future__ import unicode_literals - +""" +Renders the command line on the console. +(Redraws parts of the input line that were changed.) +""" +from __future__ import unicode_literals + from prompt_toolkit.filters import to_cli_filter from prompt_toolkit.layout.mouse_handlers import MouseHandlers -from prompt_toolkit.layout.screen import Point, Screen, WritePosition -from prompt_toolkit.output import Output +from prompt_toolkit.layout.screen import Point, Screen, WritePosition +from prompt_toolkit.output import Output from prompt_toolkit.styles import Style from prompt_toolkit.token import Token -from prompt_toolkit.utils import is_windows - +from prompt_toolkit.utils import is_windows + from six.moves import range -__all__ = ( - 'Renderer', - 'print_tokens', -) - - +__all__ = ( + 'Renderer', + 'print_tokens', +) + + def _output_screen_diff(output, screen, current_pos, previous_screen=None, last_token=None, is_done=False, use_alternate_screen=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. + """ + 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_token: `Token` 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. + 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_token = [last_token] # nonlocal - - #: Variable for capturing the output. - write = output.write + + #: Variable for capturing the output. + write = output.write write_raw = output.write_raw - - # Create locals for the most used output methods. - # (Save expensive attribute lookups.) - _output_set_attributes = output.set_attributes - _output_reset_attributes = output.reset_attributes - _output_cursor_forward = output.cursor_forward - _output_cursor_up = output.cursor_up - _output_cursor_backward = output.cursor_backward - - # Hide cursor before rendering. (Avoid flickering.) - output.hide_cursor() - - def reset_attributes(): - " Wrapper around Output.reset_attributes. " - _output_reset_attributes() + + # 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_token[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. + + 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. the_last_token = last_token[0] if the_last_token and the_last_token == char.token: - write(char.char) - else: + write(char.char) + else: _output_set_attributes(attrs_for_token[char.token]) - write(char.char) + write(char.char) last_token[0] = char.token - + # Render for the first time: reset styling. - if not previous_screen: - reset_attributes() - + if not previous_screen: + reset_attributes() + # Disable autowrap. (When entering a the alternate screen, or anytime when # we have a prompt. - In the case of a REPL, like IPython, people can have # background threads, and it's hard for debugging if their output is not @@ -120,416 +120,416 @@ def _output_screen_diff(output, screen, current_pos, previous_screen=None, last_ if not previous_screen or not use_alternate_screen: output.disable_autowrap() - # 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. - + # 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 in range(row_count): new_row = screen.data_buffer[y] previous_row = previous_screen.data_buffer[y] zero_width_escapes_row = screen.zero_width_escapes[y] - - new_max_line_len = min(width - 1, max(new_row.keys()) if new_row else 0) - previous_max_line_len = min(width - 1, max(previous_row.keys()) if previous_row else 0) - - # Loop over the columns. - c = 0 - while c < new_max_line_len + 1: - new_char = new_row[c] - old_char = previous_row[c] - char_width = (new_char.width or 1) - - # When the old and new character at this position are different, - # draw the output. (Because of the performance, we don't call - # `Char.__ne__`, but inline the same expression.) - if new_char.char != old_char.char or new_char.token != old_char.token: - current_pos = move_cursor(Point(y=y, x=c)) + + 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)) # Send injected escape sequences to output. if c in zero_width_escapes_row: write_raw(zero_width_escapes_row[c]) - output_char(new_char) - current_pos = 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) - + 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 or not use_alternate_screen: - output.enable_autowrap() - + output.enable_autowrap() + # Always reset the color attributes. This is important because a background # thread could print data to stdout and we want that to be displayed in the # default colors. (Also, if a background color has been set, many terminals # give weird artifacs on resize events.) reset_attributes() - - if screen.show_cursor or is_done: - output.show_cursor() - + + if screen.show_cursor or is_done: + output.show_cursor() + return current_pos, last_token[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 - + + +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 + # Waiting for CPR flag. True when we send the request, but didn't got a # response. self.waiting_for_cpr = False - self.reset(_scroll=True) - + self.reset(_scroll=True) + def reset(self, _scroll=False, leave_alternate_screen=True): - # Reset position - self._cursor_pos = Point(x=0, y=0) - - # Remember the last screen instance between renderers. This way, - # we can create a `diff` between two screens and only output the - # difference. It's also to remember the last height. (To show for - # instance a toolbar at the bottom position.) - self._last_screen = None - self._last_size = None + # 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_token = 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. + + # 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 and leave_alternate_screen: - self.output.quit_alternate_screen() - self._in_alternate_screen = False - - # Disable mouse support. - if self._mouse_support_enabled: - self.output.disable_mouse_support() - self._mouse_support_enabled = False - - # Disable bracketed paste. - if self._bracketed_paste_enabled: - self.output.disable_bracketed_paste() - self._bracketed_paste_enabled = False - - # Flush output. `disable_mouse_support` needs to write to stdout. - self.output.flush() - - @property - def 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.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.waiting_for_cpr = True - 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 - + 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 + self.waiting_for_cpr = False - 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. + 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_token = _output_screen_diff( - output, screen, self._cursor_pos, + output, screen, self._cursor_pos, self._last_screen, self._last_token, is_done, use_alternate_screen=self.use_alternate_screen, - 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() - + 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, leave_alternate_screen=True, erase_title=True): - """ - Hide all output and put the cursor back at the first line. This is for - instance used for running a system command (while hiding the CLI) and - later resuming the same CLI.) + """ + Hide all output and put the cursor back at the first line. This is for + instance used for running a system command (while hiding the CLI) and + later resuming the same CLI.) :param leave_alternate_screen: When True, and when inside an alternate screen buffer, quit the alternate screen. :param erase_title: When True, clear the title from the title bar. - """ - output = self.output - - output.cursor_backward(self._cursor_pos.x) - output.cursor_up(self._cursor_pos.y) - output.erase_down() - output.reset_attributes() + """ + output = self.output + + output.cursor_backward(self._cursor_pos.x) + output.cursor_up(self._cursor_pos.y) + output.erase_down() + output.reset_attributes() output.enable_autowrap() - output.flush() - - # Erase title. + output.flush() + + # Erase title. if self._last_title and erase_title: - output.clear_title() - + output.clear_title() + self.reset(leave_alternate_screen=leave_alternate_screen) - - def clear(self): - """ - Clear screen and go to 0,0 - """ - # Erase current output first. - self.erase() - - # Send "Erase Screen" command and go to (0, 0). - output = self.output - - output.erase_screen() - output.cursor_goto(0, 0) - output.flush() - - self.request_absolute_cursor_position() - - -def print_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() + + 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() diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/search_state.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/search_state.py index 3c494ea1a9..3d42921531 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/search_state.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/search_state.py @@ -1,36 +1,36 @@ -from .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) +from .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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/selection.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/selection.py index 6582921222..a71211bff8 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/selection.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/selection.py @@ -1,47 +1,47 @@ -""" -Data structures for the selection. -""" -from __future__ import unicode_literals - -__all__ = ( - 'SelectionType', +""" +Data structures for the selection. +""" +from __future__ import unicode_literals + +__all__ = ( + 'SelectionType', 'PasteMode', - 'SelectionState', -) - - -class SelectionType(object): - """ - Type of selection. - """ - #: Characters. (Visual in Vi.) + 'SelectionState', +) + + +class SelectionType(object): + """ + Type of selection. + """ + #: Characters. (Visual in Vi.) CHARACTERS = 'CHARACTERS' - - #: Whole lines. (Visual-Line in Vi.) + + #: Whole lines. (Visual-Line in Vi.) LINES = 'LINES' - - #: A block selection. (Visual-Block in Vi.) + + #: A block selection. (Visual-Block in Vi.) BLOCK = 'BLOCK' - - + + class PasteMode(object): EMACS = 'EMACS' # Yank like emacs. VI_AFTER = 'VI_AFTER' # When pressing 'p' in Vi. VI_BEFORE = 'VI_BEFORE' # When pressing 'P' in Vi. -class SelectionState(object): - """ - State of the current selection. - - :param original_cursor_position: int - :param type: :class:`~.SelectionType` - """ - def __init__(self, original_cursor_position=0, type=SelectionType.CHARACTERS): - self.original_cursor_position = original_cursor_position - self.type = type - - def __repr__(self): - return '%s(original_cursor_position=%r, type=%r)' % ( - self.__class__.__name__, - self.original_cursor_position, self.type) +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) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py index 9893624c6e..79002b5c10 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/shortcuts.py @@ -1,60 +1,60 @@ -""" -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 +""" +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, EditingMode -from .filters import IsDone, HasFocus, RendererHeightIsKnown, to_simple_filter, to_cli_filter, Condition -from .history import InMemoryHistory -from .interface import CommandLineInterface, Application, AbortAction +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.defaults import load_key_bindings_for_prompt from .key_binding.registry import Registry from .keys import Keys from .layout import Window, HSplit, 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.containers import ConditionalContainer +from .layout.controls import BufferControl, TokenListControl +from .layout.dimension import LayoutDimension +from .layout.lexers import PygmentsLexer from .layout.margins import PromptMargin, ConditionalMargin -from .layout.menus import CompletionsMenu, MultiColumnCompletionsMenu +from .layout.menus import CompletionsMenu, MultiColumnCompletionsMenu from .layout.processors import PasswordProcessor, ConditionalProcessor, AppendAutoSuggestion, HighlightSearchProcessor, HighlightSelectionProcessor, DisplayMultipleCursors -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 .layout.prompt import DefaultPrompt +from .layout.screen import Char +from .layout.toolbars import ValidationToolbar, SystemToolbar, ArgToolbar, SearchToolbar +from .layout.utils import explode_tokens from .renderer import print_tokens as renderer_print_tokens from .styles import DEFAULT_STYLE, Style, style_from_dict from .token import Token -from .utils import is_conemu_ansi, is_windows, DummyContext - +from .utils import is_conemu_ansi, is_windows, DummyContext + from six import text_type, exec_, PY2 - + import os -import sys -import textwrap +import sys +import textwrap import threading import time - + try: from pygments.lexer import Lexer as pygments_Lexer from pygments.style import Style as pygments_Style @@ -62,124 +62,124 @@ except ImportError: pygments_Lexer = None pygments_Style = None -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', +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', 'create_confirm_application', 'run_application', 'confirm', 'print_tokens', 'clear', -) - - +) + + def create_eventloop(inputhook=None, recognize_win32_paste=True): - """ - 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 + """ + 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 return Loop(inputhook=inputhook, recognize_paste=recognize_win32_paste) - else: - from prompt_toolkit.eventloop.posix import PosixEventLoop as Loop + else: + from prompt_toolkit.eventloop.posix import PosixEventLoop as Loop return Loop(inputhook=inputhook) - - + + def create_output(stdout=None, true_color=False, ansi_colors_only=None): - """ - 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`.) + """ + 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`.) :param ansi_colors_only: When True, restrict to 16 ANSI colors only. (`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: + """ + 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: term = os.environ.get('TERM', '') if PY2: term = term.decode('utf-8') - + return Vt100_Output.from_pty( stdout, true_color=true_color, ansi_colors_only=ansi_colors_only, term=term) - - -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): - """ + + +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 three new functions instead. One that tells whether this prompt consists of multiple lines; 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 has_before_tokens(cli): for token, char in get_prompt_tokens(cli): if '\n' in char: return True return False - def before(cli): - result = [] - found_nl = False - for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): + def before(cli): + result = [] + found_nl = False + for token, char in reversed(explode_tokens(get_prompt_tokens(cli))): if found_nl: result.insert(0, (token, char)) elif char == '\n': - found_nl = True - 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 - + found_nl = True + 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 has_before_tokens, before, first_input_line - - + + class _RPrompt(Window): " The prompt that is displayed on the right side of the Window. " def __init__(self, get_tokens=None): @@ -189,109 +189,109 @@ class _RPrompt(Window): TokenListControl(get_tokens, align_right=True)) -def create_prompt_layout(message='', lexer=None, is_password=False, - reserve_space_for_menu=8, +def create_prompt_layout(message='', lexer=None, is_password=False, + reserve_space_for_menu=8, get_prompt_tokens=None, get_continuation_tokens=None, get_rprompt_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`.) + 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_continuation_tokens: An optional callable that takes a CommandLineInterface and width as input and returns a list of (Token, text) tuples to be used for the continuation. - :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. - """ + :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), 'Please provide a unicode string.' - 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 get_bottom_toolbar_tokens is None or callable(get_bottom_toolbar_tokens) + assert get_prompt_tokens is None or callable(get_prompt_tokens) assert get_rprompt_tokens is None or callable(get_rprompt_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)] - + 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)] + has_before_tokens, 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: + + # `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 pygments_Lexer and issubclass(lexer, pygments_Lexer): lexer = PygmentsLexer(lexer, sync_from_start=True) - except TypeError: # Happens when lexer is `None` or an instance of something else. - pass - + except TypeError: # Happens when lexer is `None` or an instance of something else. + pass + # Create processors list. input_processors = [ ConditionalProcessor( - # 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.) + # 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.) HighlightSearchProcessor(preview_search=True), HasFocus(SEARCH_BUFFER)), HighlightSelectionProcessor(), - ConditionalProcessor(AppendAutoSuggestion(), HasFocus(DEFAULT_BUFFER) & ~IsDone()), + ConditionalProcessor(AppendAutoSuggestion(), HasFocus(DEFAULT_BUFFER) & ~IsDone()), ConditionalProcessor(PasswordProcessor(), is_password), DisplayMultipleCursors(DEFAULT_BUFFER), ] - - 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( + + 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_2), ~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: + + # 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: buff = cli.current_buffer - + # Reserve the space, either when there are completions, or when # `complete_while_typing` is true and we expect completions very # soon. @@ -300,8 +300,8 @@ def create_prompt_layout(message='', lexer=None, is_password=False, return LayoutDimension() - # Create and return Container instance. - return HSplit([ + # Create and return Container instance. + return HSplit([ # The main input, with completion menus floating on top of it. FloatContainer( HSplit([ @@ -310,15 +310,15 @@ def create_prompt_layout(message='', lexer=None, is_password=False, TokenListControl(get_prompt_tokens_1), dont_extend_height=True), Condition(has_before_tokens) - ), - Window( - BufferControl( - input_processors=input_processors, - lexer=lexer, - # Enable preview_search, we want to have immediate feedback - # in reverse-i-search mode. - preview_search=True), - get_height=get_height, + ), + Window( + BufferControl( + input_processors=input_processors, + lexer=lexer, + # Enable preview_search, we want to have immediate feedback + # in reverse-i-search mode. + preview_search=True), + get_height=get_height, left_margins=[ # In multiline mode, use the window margin to display # the prompt and continuation tokens. @@ -328,7 +328,7 @@ def create_prompt_layout(message='', lexer=None, is_password=False, ) ], wrap_lines=wrap_lines, - ), + ), ]), [ # Completion menus. @@ -351,193 +351,193 @@ def create_prompt_layout(message='', lexer=None, is_password=False, content=_RPrompt(get_rprompt_tokens)), ] ), - 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, + 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, editing_mode=EditingMode.EMACS, - 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, + 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_continuation_tokens=None, get_rprompt_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, + 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, erase_when_done=False, - 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. + 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 editing_mode: ``EditingMode.VI`` or ``EditingMode.EMACS``. :param vi_mode: `bool`, if True, Identical to ``editing_mode=EditingMode.VI``. - :param complete_while_typing: `bool` or + :param complete_while_typing: `bool` or :class:`~prompt_toolkit.filters.SimpleFilter`. Enable autocompletion while typing. - :param enable_history_search: `bool` or + :param enable_history_search: `bool` or :class:`~prompt_toolkit.filters.SimpleFilter`. 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. + 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: :class:`.Style` instance 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: + :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 = load_key_bindings_for_prompt( - enable_system_bindings=enable_system_bindings, + enable_system_bindings=enable_system_bindings, enable_open_in_editor=enable_open_in_editor) - + # Ensure backwards-compatibility, when `vi_mode` is passed. if vi_mode: editing_mode = EditingMode.VI - # 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: + # 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 pygments_Style and issubclass(style, pygments_Style): style = style_from_dict(style.styles) 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, + 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_continuation_tokens=get_continuation_tokens, get_rprompt_tokens=get_rprompt_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, + 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, editing_mode=editing_mode, erase_when_done=erase_when_done, reverse_vi_search_direction=True, - 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. + 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. :param refresh_interval: (number; in seconds) When given, refresh the UI every so many seconds. - """ - patch_stdout = kwargs.pop('patch_stdout', False) - return_asyncio_coroutine = kwargs.pop('return_asyncio_coroutine', False) - true_color = kwargs.pop('true_color', False) + """ + patch_stdout = kwargs.pop('patch_stdout', False) + return_asyncio_coroutine = kwargs.pop('return_asyncio_coroutine', False) + true_color = kwargs.pop('true_color', False) refresh_interval = kwargs.pop('refresh_interval', 0) eventloop = kwargs.pop('eventloop', None) - + application = create_prompt_application(message, **kwargs) return run_application(application, @@ -564,17 +564,17 @@ def run_application( """ assert isinstance(application, Application) - if return_asyncio_coroutine: - eventloop = create_asyncio_eventloop() - else: + if return_asyncio_coroutine: + eventloop = create_asyncio_eventloop() + else: eventloop = eventloop or create_eventloop() - - # Create CommandLineInterface. - cli = CommandLineInterface( + + # Create CommandLineInterface. + cli = CommandLineInterface( application=application, - eventloop=eventloop, - output=create_output(true_color=true_color)) - + eventloop=eventloop, + output=create_output(true_color=true_color)) + # Set up refresh interval. if refresh_interval: done = [False] @@ -593,20 +593,20 @@ def run_application( cli.on_start += start_refresh_loop cli.on_stop += stop_refresh_loop - # Replace stdout. + # Replace stdout. patch_context = cli.patch_stdout_context(raw=True) if patch_stdout else DummyContext() - - # Read input and return it. - if return_asyncio_coroutine: - # Create an asyncio coroutine and call it. + + # Read input and return it. + if return_asyncio_coroutine: + # Create an asyncio coroutine and call it. exec_context = {'patch_context': patch_context, 'cli': cli, 'Document': Document} - exec_(textwrap.dedent(''' - def prompt_coro(): + exec_(textwrap.dedent(''' + def prompt_coro(): # Inline import, because it slows down startup when asyncio is not # needed. import asyncio - + @asyncio.coroutine def run(): with patch_context: @@ -616,29 +616,29 @@ def run_application( return result.text return result return run() - '''), exec_context) - - return exec_context['prompt_coro']() - else: - try: - with patch_context: + '''), exec_context) + + return exec_context['prompt_coro']() + else: + try: + with patch_context: result = cli.run() - + if isinstance(result, Document): # Backwards-compatibility. return result.text return result - 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) - - + 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) + + def create_confirm_application(message): """ Create a confirmation `Application` that returns True/False. @@ -709,8 +709,8 @@ def clear(): out.flush() -# Deprecated alias for `prompt`. -get_input = prompt +# Deprecated alias for `prompt`. +get_input = prompt # Deprecated alias for create_prompt_layout create_default_layout = create_prompt_layout # Deprecated alias for create_prompt_application diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/conemu_output.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/conemu_output.py index 16b7bde8f3..b2e724c184 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/conemu_output.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/conemu_output.py @@ -1,42 +1,42 @@ -from __future__ import unicode_literals - -from prompt_toolkit.renderer import Output - -from .win32_output import Win32Output -from .vt100_output import Vt100_Output - -__all__ = ( - 'ConEmuOutput', -) - - -class ConEmuOutput(object): - """ - ConEmu (Windows) output abstraction. - - ConEmu is a Windows console application, but it also supports ANSI escape - sequences. This output class is actually a proxy to both `Win32Output` and - `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but - all cursor movements and scrolling happens through the `Vt100_Output`. - - This way, we can have 256 colors in ConEmu and Cmder. Rendering will be - even a little faster as well. - - http://conemu.github.io/ - http://gooseberrycreative.com/cmder/ - """ - def __init__(self, stdout): - self.win32_output = Win32Output(stdout) - self.vt100_output = Vt100_Output(stdout, lambda: None) - - def __getattr__(self, name): - if name in ('get_size', 'get_rows_below_cursor_position', - 'enable_mouse_support', 'disable_mouse_support', +from __future__ import unicode_literals + +from prompt_toolkit.renderer import Output + +from .win32_output import Win32Output +from .vt100_output import Vt100_Output + +__all__ = ( + 'ConEmuOutput', +) + + +class ConEmuOutput(object): + """ + ConEmu (Windows) output abstraction. + + ConEmu is a Windows console application, but it also supports ANSI escape + sequences. This output class is actually a proxy to both `Win32Output` and + `Vt100_Output`. It uses `Win32Output` for console sizing and scrolling, but + all cursor movements and scrolling happens through the `Vt100_Output`. + + This way, we can have 256 colors in ConEmu and Cmder. Rendering will be + even a little faster as well. + + http://conemu.github.io/ + http://gooseberrycreative.com/cmder/ + """ + def __init__(self, stdout): + self.win32_output = Win32Output(stdout) + self.vt100_output = Vt100_Output(stdout, lambda: None) + + def __getattr__(self, name): + if name in ('get_size', 'get_rows_below_cursor_position', + 'enable_mouse_support', 'disable_mouse_support', 'scroll_buffer_to_prompt', 'get_win32_screen_buffer_info', 'enable_bracketed_paste', 'disable_bracketed_paste'): - return getattr(self.win32_output, name) - else: - return getattr(self.vt100_output, name) - - -Output.register(ConEmuOutput) + return getattr(self.win32_output, name) + else: + return getattr(self.vt100_output, name) + + +Output.register(ConEmuOutput) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_input.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_input.py index 74841312fa..846929807a 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_input.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_input.py @@ -1,137 +1,137 @@ -""" -Parser for VT100 input stream. -""" -from __future__ import unicode_literals - -import os -import re -import six -import termios -import tty - +""" +Parser for VT100 input stream. +""" +from __future__ import unicode_literals + +import os +import re +import six +import termios +import tty + from six.moves import range -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'(<?[\d;]+[mM]|M...)\Z') - -# Regex matching any valid prefix of a CPR response. -# (Note that it doesn't contain the last character, the 'R'. The prefix has to -# be shorter.) -_cpr_response_prefix_re = re.compile('^' + re.escape('\x1b[') + r'[\d;]*\Z') - -_mouse_event_prefix_re = re.compile('^' + re.escape('\x1b[') + r'(<?[\d;]*|M.{0,2})\Z') - - -class _Flush(object): - """ Helper object to indicate flush operation to the parser. """ - pass - - -# Mapping of vt100 escape codes to Keys. -ANSI_SEQUENCES = { - '\x1b': Keys.Escape, - - '\x00': Keys.ControlSpace, # Control-Space (Also for Ctrl-@) - '\x01': Keys.ControlA, # Control-A (home) - '\x02': Keys.ControlB, # Control-B (emacs cursor left) - '\x03': Keys.ControlC, # Control-C (interrupt) - '\x04': Keys.ControlD, # Control-D (exit) - '\x05': Keys.ControlE, # Contrel-E (end) - '\x06': Keys.ControlF, # Control-F (cursor forward) - '\x07': Keys.ControlG, # Control-G - '\x08': Keys.ControlH, # Control-H (8) (Identical to '\b') - '\x09': Keys.ControlI, # Control-I (9) (Identical to '\t') - '\x0a': Keys.ControlJ, # Control-J (10) (Identical to '\n') - '\x0b': Keys.ControlK, # Control-K (delete until end of line; vertical tab) - '\x0c': Keys.ControlL, # Control-L (clear; form feed) - '\x0d': Keys.ControlM, # Control-M (13) (Identical to '\r') - '\x0e': Keys.ControlN, # Control-N (14) (history forward) - '\x0f': Keys.ControlO, # Control-O (15) - '\x10': Keys.ControlP, # Control-P (16) (history back) - '\x11': Keys.ControlQ, # Control-Q - '\x12': Keys.ControlR, # Control-R (18) (reverse search) - '\x13': Keys.ControlS, # Control-S (19) (forward search) - '\x14': Keys.ControlT, # Control-T - '\x15': Keys.ControlU, # Control-U - '\x16': Keys.ControlV, # Control-V - '\x17': Keys.ControlW, # Control-W - '\x18': Keys.ControlX, # Control-X - '\x19': Keys.ControlY, # Control-Y (25) - '\x1a': Keys.ControlZ, # Control-Z - - '\x1c': Keys.ControlBackslash, # Both Control-\ and Ctrl-| - '\x1d': Keys.ControlSquareClose, # Control-] - '\x1e': Keys.ControlCircumflex, # Control-^ - '\x1f': Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hypen.) - '\x7f': Keys.Backspace, # (127) Backspace - '\x1b[A': Keys.Up, - '\x1b[B': Keys.Down, - '\x1b[C': Keys.Right, - '\x1b[D': Keys.Left, - '\x1b[H': Keys.Home, - '\x1bOH': Keys.Home, - '\x1b[F': Keys.End, - '\x1bOF': Keys.End, - '\x1b[3~': Keys.Delete, - '\x1b[3;2~': Keys.ShiftDelete, # xterm, gnome-terminal. +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'(<?[\d;]+[mM]|M...)\Z') + +# Regex matching any valid prefix of a CPR response. +# (Note that it doesn't contain the last character, the 'R'. The prefix has to +# be shorter.) +_cpr_response_prefix_re = re.compile('^' + re.escape('\x1b[') + r'[\d;]*\Z') + +_mouse_event_prefix_re = re.compile('^' + re.escape('\x1b[') + r'(<?[\d;]*|M.{0,2})\Z') + + +class _Flush(object): + """ Helper object to indicate flush operation to the parser. """ + pass + + +# Mapping of vt100 escape codes to Keys. +ANSI_SEQUENCES = { + '\x1b': Keys.Escape, + + '\x00': Keys.ControlSpace, # Control-Space (Also for Ctrl-@) + '\x01': Keys.ControlA, # Control-A (home) + '\x02': Keys.ControlB, # Control-B (emacs cursor left) + '\x03': Keys.ControlC, # Control-C (interrupt) + '\x04': Keys.ControlD, # Control-D (exit) + '\x05': Keys.ControlE, # Contrel-E (end) + '\x06': Keys.ControlF, # Control-F (cursor forward) + '\x07': Keys.ControlG, # Control-G + '\x08': Keys.ControlH, # Control-H (8) (Identical to '\b') + '\x09': Keys.ControlI, # Control-I (9) (Identical to '\t') + '\x0a': Keys.ControlJ, # Control-J (10) (Identical to '\n') + '\x0b': Keys.ControlK, # Control-K (delete until end of line; vertical tab) + '\x0c': Keys.ControlL, # Control-L (clear; form feed) + '\x0d': Keys.ControlM, # Control-M (13) (Identical to '\r') + '\x0e': Keys.ControlN, # Control-N (14) (history forward) + '\x0f': Keys.ControlO, # Control-O (15) + '\x10': Keys.ControlP, # Control-P (16) (history back) + '\x11': Keys.ControlQ, # Control-Q + '\x12': Keys.ControlR, # Control-R (18) (reverse search) + '\x13': Keys.ControlS, # Control-S (19) (forward search) + '\x14': Keys.ControlT, # Control-T + '\x15': Keys.ControlU, # Control-U + '\x16': Keys.ControlV, # Control-V + '\x17': Keys.ControlW, # Control-W + '\x18': Keys.ControlX, # Control-X + '\x19': Keys.ControlY, # Control-Y (25) + '\x1a': Keys.ControlZ, # Control-Z + + '\x1c': Keys.ControlBackslash, # Both Control-\ and Ctrl-| + '\x1d': Keys.ControlSquareClose, # Control-] + '\x1e': Keys.ControlCircumflex, # Control-^ + '\x1f': Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hypen.) + '\x7f': Keys.Backspace, # (127) Backspace + '\x1b[A': Keys.Up, + '\x1b[B': Keys.Down, + '\x1b[C': Keys.Right, + '\x1b[D': Keys.Left, + '\x1b[H': Keys.Home, + '\x1bOH': Keys.Home, + '\x1b[F': Keys.End, + '\x1bOF': Keys.End, + '\x1b[3~': Keys.Delete, + '\x1b[3;2~': Keys.ShiftDelete, # xterm, gnome-terminal. '\x1b[3;5~': Keys.ControlDelete, # xterm, gnome-terminal. - '\x1b[1~': Keys.Home, # tmux - '\x1b[4~': Keys.End, # tmux - '\x1b[5~': Keys.PageUp, - '\x1b[6~': Keys.PageDown, - '\x1b[7~': Keys.Home, # xrvt - '\x1b[8~': Keys.End, # xrvt - '\x1b[Z': Keys.BackTab, # shift + tab - '\x1b[2~': Keys.Insert, - - '\x1bOP': Keys.F1, - '\x1bOQ': Keys.F2, - '\x1bOR': Keys.F3, - '\x1bOS': Keys.F4, + '\x1b[1~': Keys.Home, # tmux + '\x1b[4~': Keys.End, # tmux + '\x1b[5~': Keys.PageUp, + '\x1b[6~': Keys.PageDown, + '\x1b[7~': Keys.Home, # xrvt + '\x1b[8~': Keys.End, # xrvt + '\x1b[Z': Keys.BackTab, # shift + tab + '\x1b[2~': Keys.Insert, + + '\x1bOP': Keys.F1, + '\x1bOQ': Keys.F2, + '\x1bOR': Keys.F3, + '\x1bOS': Keys.F4, '\x1b[[A': Keys.F1, # Linux console. '\x1b[[B': Keys.F2, # Linux console. '\x1b[[C': Keys.F3, # Linux console. '\x1b[[D': Keys.F4, # Linux console. '\x1b[[E': Keys.F5, # Linux console. - '\x1b[11~': Keys.F1, # rxvt-unicode - '\x1b[12~': Keys.F2, # rxvt-unicode - '\x1b[13~': Keys.F3, # rxvt-unicode - '\x1b[14~': Keys.F4, # rxvt-unicode - '\x1b[15~': Keys.F5, - '\x1b[17~': Keys.F6, - '\x1b[18~': Keys.F7, - '\x1b[19~': Keys.F8, - '\x1b[20~': Keys.F9, - '\x1b[21~': Keys.F10, - '\x1b[23~': Keys.F11, - '\x1b[24~': Keys.F12, - '\x1b[25~': Keys.F13, - '\x1b[26~': Keys.F14, - '\x1b[28~': Keys.F15, - '\x1b[29~': Keys.F16, - '\x1b[31~': Keys.F17, - '\x1b[32~': Keys.F18, - '\x1b[33~': Keys.F19, - '\x1b[34~': Keys.F20, + '\x1b[11~': Keys.F1, # rxvt-unicode + '\x1b[12~': Keys.F2, # rxvt-unicode + '\x1b[13~': Keys.F3, # rxvt-unicode + '\x1b[14~': Keys.F4, # rxvt-unicode + '\x1b[15~': Keys.F5, + '\x1b[17~': Keys.F6, + '\x1b[18~': Keys.F7, + '\x1b[19~': Keys.F8, + '\x1b[20~': Keys.F9, + '\x1b[21~': Keys.F10, + '\x1b[23~': Keys.F11, + '\x1b[24~': Keys.F12, + '\x1b[25~': Keys.F13, + '\x1b[26~': Keys.F14, + '\x1b[28~': Keys.F15, + '\x1b[29~': Keys.F16, + '\x1b[31~': Keys.F17, + '\x1b[32~': Keys.F18, + '\x1b[33~': Keys.F19, + '\x1b[34~': Keys.F20, # Xterm '\x1b[1;2P': Keys.F13, @@ -147,11 +147,11 @@ ANSI_SEQUENCES = { '\x1b[23;2~': Keys.F23, '\x1b[24;2~': Keys.F24, - '\x1b[1;5A': Keys.ControlUp, # Cursor Mode - '\x1b[1;5B': Keys.ControlDown, # Cursor Mode - '\x1b[1;5C': Keys.ControlRight, # Cursor Mode - '\x1b[1;5D': Keys.ControlLeft, # Cursor Mode - + '\x1b[1;5A': Keys.ControlUp, # Cursor Mode + '\x1b[1;5B': Keys.ControlDown, # Cursor Mode + '\x1b[1;5C': Keys.ControlRight, # Cursor Mode + '\x1b[1;5D': Keys.ControlLeft, # Cursor Mode + '\x1b[1;2A': Keys.ShiftUp, '\x1b[1;2B': Keys.ShiftDown, '\x1b[1;2C': Keys.ShiftRight, @@ -165,224 +165,224 @@ ANSI_SEQUENCES = { '\x1bOC': Keys.Right, '\x1bOD': Keys.Left, - '\x1b[5A': Keys.ControlUp, - '\x1b[5B': Keys.ControlDown, - '\x1b[5C': Keys.ControlRight, - '\x1b[5D': Keys.ControlLeft, - + '\x1b[5A': Keys.ControlUp, + '\x1b[5B': Keys.ControlDown, + '\x1b[5C': Keys.ControlRight, + '\x1b[5D': Keys.ControlLeft, + '\x1bOc': Keys.ControlRight, # rxvt '\x1bOd': Keys.ControlLeft, # rxvt - '\x1b[200~': Keys.BracketedPaste, # Start of bracketed paste. - - # Meta + arrow keys. Several terminals handle this differently. - # The following sequences are for xterm and gnome-terminal. - # (Iterm sends ESC followed by the normal arrow_up/down/left/right - # sequences, and the OSX Terminal sends ESCb and ESCf for "alt - # arrow_left" and "alt arrow_right." We don't handle these - # explicitely, in here, because would could not distinguesh between - # pressing ESC (to go to Vi navigation mode), followed by just the - # 'b' or 'f' key. These combinations are handled in - # the input processor.) - '\x1b[1;3D': (Keys.Escape, Keys.Left), - '\x1b[1;3C': (Keys.Escape, Keys.Right), - '\x1b[1;3A': (Keys.Escape, Keys.Up), - '\x1b[1;3B': (Keys.Escape, Keys.Down), + '\x1b[200~': Keys.BracketedPaste, # Start of bracketed paste. + + # Meta + arrow keys. Several terminals handle this differently. + # The following sequences are for xterm and gnome-terminal. + # (Iterm sends ESC followed by the normal arrow_up/down/left/right + # sequences, and the OSX Terminal sends ESCb and ESCf for "alt + # arrow_left" and "alt arrow_right." We don't handle these + # explicitely, in here, because would could not distinguesh between + # pressing ESC (to go to Vi navigation mode), followed by just the + # 'b' or 'f' key. These combinations are handled in + # the input processor.) + '\x1b[1;3D': (Keys.Escape, Keys.Left), + '\x1b[1;3C': (Keys.Escape, Keys.Right), + '\x1b[1;3A': (Keys.Escape, Keys.Up), + '\x1b[1;3B': (Keys.Escape, Keys.Down), # Sequences generated by numpad 5. Not sure what it means. (It doesn't # appear in 'infocmp'. Just ignore. '\x1b[E': Keys.Ignore, # Xterm. '\x1b[G': Keys.Ignore, # Linux console. -} - - -class _IsPrefixOfLongerMatchCache(dict): - """ - Dictiory that maps input sequences to a boolean indicating whether there is - any key that start with this characters. - """ - def __missing__(self, prefix): - # (hard coded) If this could be a prefix of a CPR response, return - # True. - if (_cpr_response_prefix_re.match(prefix) or _mouse_event_prefix_re.match(prefix)): - result = True - else: - # If this could be a prefix of anything else, also return True. - result = any(v for k, v in ANSI_SEQUENCES.items() if k.startswith(prefix) and k != prefix) - - self[prefix] = result - return result - - -_IS_PREFIX_OF_LONGER_MATCH_CACHE = _IsPrefixOfLongerMatchCache() - - -class InputStream(object): - """ - Parser for VT100 input stream. - - Feed the data through the `feed` method and the correct callbacks of the - `input_processor` will be called. - - :: - - def callback(key): - pass - i = InputStream(callback) - i.feed('data\x01...') - - :attr input_processor: :class:`~prompt_toolkit.key_binding.InputProcessor` instance. - """ - # Lookup table of ANSI escape sequences for a VT100 terminal - # Hint: in order to know what sequences your terminal writes to stdin, run - # "od -c" and start typing. - def __init__(self, feed_key_callback): - assert callable(feed_key_callback) - - self.feed_key_callback = feed_key_callback - self.reset() - - if _DEBUG_RENDERER_INPUT: - self.LOG = open(_DEBUG_RENDERER_INPUT_FILENAME, 'ab') - - def reset(self, request=False): - self._in_bracketed_paste = False - self._start_parser() - - def _start_parser(self): - """ - Start the parser coroutine. - """ - self._input_parser = self._input_parser_generator() - self._input_parser.send(None) - - def _get_match(self, prefix): - """ - Return the key that maps to this prefix. - """ - # (hard coded) If we match a CPR response, return Keys.CPRResponse. - # (This one doesn't fit in the ANSI_SEQUENCES, because it contains - # integer variables.) - if _cpr_response_re.match(prefix): - return Keys.CPRResponse - - elif _mouse_event_re.match(prefix): - return Keys.Vt100MouseEvent - - # Otherwise, use the mappings. - try: - return ANSI_SEQUENCES[prefix] - except KeyError: - return None - - def _input_parser_generator(self): - """ - Coroutine (state machine) for the input parser. - """ - prefix = '' - retry = False - flush = False - - while True: - flush = False - - if retry: - retry = False - else: - # Get next character. - c = yield - - if c == _Flush: - flush = True - else: - prefix += c - - # If we have some data, check for matches. - if prefix: - is_prefix_of_longer_match = _IS_PREFIX_OF_LONGER_MATCH_CACHE[prefix] - match = self._get_match(prefix) - - # Exact matches found, call handlers.. - if (flush or not is_prefix_of_longer_match) and match: - self._call_handler(match, prefix) - prefix = '' - - # No exact match found. - elif (flush or not is_prefix_of_longer_match) and not match: - found = False - retry = True - - # Loop over the input, try the longest match first and - # shift. - for i in range(len(prefix), 0, -1): - match= self._get_match(prefix[:i]) - if match: - self._call_handler(match, prefix[:i]) - prefix = prefix[i:] - found = True - - if not found: - self._call_handler(prefix[0], prefix[0]) - prefix = prefix[1:] - - def _call_handler(self, key, insert_text): - """ - Callback to handler. - """ - if isinstance(key, tuple): - for k in key: - self._call_handler(k, insert_text) - else: - if key == Keys.BracketedPaste: - self._in_bracketed_paste = True - self._paste_buffer = '' - else: - self.feed_key_callback(KeyPress(key, insert_text)) - - def feed(self, data): - """ - Feed the input stream. - - :param data: Input string (unicode). - """ - assert isinstance(data, six.text_type) - - if _DEBUG_RENDERER_INPUT: - self.LOG.write(repr(data).encode('utf-8') + b'\n') - self.LOG.flush() - - # Handle bracketed paste. (We bypass the parser that matches all other - # key presses and keep reading input until we see the end mark.) - # This is much faster then parsing character by character. - if self._in_bracketed_paste: - self._paste_buffer += data - end_mark = '\x1b[201~' - - if end_mark in self._paste_buffer: - end_index = self._paste_buffer.index(end_mark) - - # Feed content to key bindings. - paste_content = self._paste_buffer[:end_index] - self.feed_key_callback(KeyPress(Keys.BracketedPaste, paste_content)) - - # Quit bracketed paste mode and handle remaining input. - self._in_bracketed_paste = False +} + + +class _IsPrefixOfLongerMatchCache(dict): + """ + Dictiory that maps input sequences to a boolean indicating whether there is + any key that start with this characters. + """ + def __missing__(self, prefix): + # (hard coded) If this could be a prefix of a CPR response, return + # True. + if (_cpr_response_prefix_re.match(prefix) or _mouse_event_prefix_re.match(prefix)): + result = True + else: + # If this could be a prefix of anything else, also return True. + result = any(v for k, v in ANSI_SEQUENCES.items() if k.startswith(prefix) and k != prefix) + + self[prefix] = result + return result + + +_IS_PREFIX_OF_LONGER_MATCH_CACHE = _IsPrefixOfLongerMatchCache() + + +class InputStream(object): + """ + Parser for VT100 input stream. + + Feed the data through the `feed` method and the correct callbacks of the + `input_processor` will be called. + + :: + + def callback(key): + pass + i = InputStream(callback) + i.feed('data\x01...') + + :attr input_processor: :class:`~prompt_toolkit.key_binding.InputProcessor` instance. + """ + # Lookup table of ANSI escape sequences for a VT100 terminal + # Hint: in order to know what sequences your terminal writes to stdin, run + # "od -c" and start typing. + def __init__(self, feed_key_callback): + assert callable(feed_key_callback) + + self.feed_key_callback = feed_key_callback + self.reset() + + if _DEBUG_RENDERER_INPUT: + self.LOG = open(_DEBUG_RENDERER_INPUT_FILENAME, 'ab') + + def reset(self, request=False): + self._in_bracketed_paste = False + self._start_parser() + + def _start_parser(self): + """ + Start the parser coroutine. + """ + self._input_parser = self._input_parser_generator() + self._input_parser.send(None) + + def _get_match(self, prefix): + """ + Return the key that maps to this prefix. + """ + # (hard coded) If we match a CPR response, return Keys.CPRResponse. + # (This one doesn't fit in the ANSI_SEQUENCES, because it contains + # integer variables.) + if _cpr_response_re.match(prefix): + return Keys.CPRResponse + + elif _mouse_event_re.match(prefix): + return Keys.Vt100MouseEvent + + # Otherwise, use the mappings. + try: + return ANSI_SEQUENCES[prefix] + except KeyError: + return None + + def _input_parser_generator(self): + """ + Coroutine (state machine) for the input parser. + """ + prefix = '' + retry = False + flush = False + + while True: + flush = False + + if retry: + retry = False + else: + # Get next character. + c = yield + + if c == _Flush: + flush = True + else: + prefix += c + + # If we have some data, check for matches. + if prefix: + is_prefix_of_longer_match = _IS_PREFIX_OF_LONGER_MATCH_CACHE[prefix] + match = self._get_match(prefix) + + # Exact matches found, call handlers.. + if (flush or not is_prefix_of_longer_match) and match: + self._call_handler(match, prefix) + prefix = '' + + # No exact match found. + elif (flush or not is_prefix_of_longer_match) and not match: + found = False + retry = True + + # Loop over the input, try the longest match first and + # shift. + for i in range(len(prefix), 0, -1): + match= self._get_match(prefix[:i]) + if match: + self._call_handler(match, prefix[:i]) + prefix = prefix[i:] + found = True + + if not found: + self._call_handler(prefix[0], prefix[0]) + prefix = prefix[1:] + + def _call_handler(self, key, insert_text): + """ + Callback to handler. + """ + if isinstance(key, tuple): + for k in key: + self._call_handler(k, insert_text) + else: + if key == Keys.BracketedPaste: + self._in_bracketed_paste = True + self._paste_buffer = '' + else: + self.feed_key_callback(KeyPress(key, insert_text)) + + def feed(self, data): + """ + Feed the input stream. + + :param data: Input string (unicode). + """ + assert isinstance(data, six.text_type) + + if _DEBUG_RENDERER_INPUT: + self.LOG.write(repr(data).encode('utf-8') + b'\n') + self.LOG.flush() + + # Handle bracketed paste. (We bypass the parser that matches all other + # key presses and keep reading input until we see the end mark.) + # This is much faster then parsing character by character. + if self._in_bracketed_paste: + self._paste_buffer += data + end_mark = '\x1b[201~' + + if end_mark in self._paste_buffer: + end_index = self._paste_buffer.index(end_mark) + + # Feed content to key bindings. + paste_content = self._paste_buffer[:end_index] + self.feed_key_callback(KeyPress(Keys.BracketedPaste, paste_content)) + + # Quit bracketed paste mode and handle remaining input. + self._in_bracketed_paste = False remaining = self._paste_buffer[end_index + len(end_mark):] - self._paste_buffer = '' - + self._paste_buffer = '' + self.feed(remaining) - # Handle normal input character by character. - else: - for i, c in enumerate(data): - if self._in_bracketed_paste: - # Quit loop and process from this position when the parser - # entered bracketed paste. - self.feed(data[i:]) - break - else: - # Replace \r by \n. (Some clients send \r instead of \n - # when enter is pressed. E.g. telnet and some other + # Handle normal input character by character. + else: + for i, c in enumerate(data): + if self._in_bracketed_paste: + # Quit loop and process from this position when the parser + # entered bracketed paste. + self.feed(data[i:]) + break + else: + # Replace \r by \n. (Some clients send \r instead of \n + # when enter is pressed. E.g. telnet and some other # terminals.) # XXX: We should remove this in a future version. It *is* @@ -393,41 +393,41 @@ class InputStream(object): # When this is removed, replace Enter=ControlJ by # Enter=ControlM in keys.py. - if c == '\r': - c = '\n' - self._input_parser.send(c) - - def flush(self): - """ - Flush the buffer of the input stream. - - This will allow us to handle the escape key (or maybe meta) sooner. - The input received by the escape key is actually the same as the first - characters of e.g. Arrow-Up, so without knowing what follows the escape - sequence, we don't know whether escape has been pressed, or whether - it's something else. This flush function should be called after a - timeout, and processes everything that's still in the buffer as-is, so - without assuming any characters will folow. - """ - self._input_parser.send(_Flush) - - def feed_and_flush(self, data): - """ - Wrapper around ``feed`` and ``flush``. - """ - self.feed(data) - self.flush() - - -class raw_mode(object): - """ - :: - - with raw_mode(stdin): - ''' the pseudo-terminal stdin is now used in raw mode ''' + if c == '\r': + c = '\n' + self._input_parser.send(c) + + def flush(self): + """ + Flush the buffer of the input stream. + + This will allow us to handle the escape key (or maybe meta) sooner. + The input received by the escape key is actually the same as the first + characters of e.g. Arrow-Up, so without knowing what follows the escape + sequence, we don't know whether escape has been pressed, or whether + it's something else. This flush function should be called after a + timeout, and processes everything that's still in the buffer as-is, so + without assuming any characters will folow. + """ + self._input_parser.send(_Flush) + + def feed_and_flush(self, data): + """ + Wrapper around ``feed`` and ``flush``. + """ + self.feed(data) + self.flush() + + +class raw_mode(object): + """ + :: + + with raw_mode(stdin): + ''' the pseudo-terminal stdin is now used in raw mode ''' We ignore errors when executing `tcgetattr` fails. - """ + """ # There are several reasons for ignoring errors: # 1. To avoid the "Inappropriate ioctl for device" crash if somebody would # execute this code (In a Python REPL, for instance): @@ -442,16 +442,16 @@ class raw_mode(object): # 2. Related, when stdin is an SSH pipe, and no full terminal was allocated. # See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/165 - def __init__(self, fileno): - self.fileno = fileno + def __init__(self, fileno): + self.fileno = fileno try: self.attrs_before = termios.tcgetattr(fileno) except termios.error: # Ignore attribute errors. self.attrs_before = None - - def __enter__(self): - # NOTE: On os X systems, using pty.setraw() fails. Therefor we are using this: + + def __enter__(self): + # NOTE: On os X systems, using pty.setraw() fails. Therefor we are using this: try: newattr = termios.tcgetattr(self.fileno) except termios.error: @@ -468,14 +468,14 @@ class raw_mode(object): newattr[tty.CC][termios.VMIN] = 1 termios.tcsetattr(self.fileno, termios.TCSANOW, newattr) - + # Put the terminal in cursor mode. (Instead of application mode.) os.write(self.fileno, b'\x1b[?1l') - + @classmethod def _patch_lflag(cls, attrs): - return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) - + return attrs & ~(termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) + @classmethod def _patch_iflag(cls, attrs): return attrs & ~( @@ -488,28 +488,28 @@ class raw_mode(object): termios.ICRNL | termios.INLCR | termios.IGNCR ) - def __exit__(self, *a, **kw): + def __exit__(self, *a, **kw): if self.attrs_before is not None: try: termios.tcsetattr(self.fileno, termios.TCSANOW, self.attrs_before) except termios.error: pass - + # # Put the terminal in application mode. # self._stdout.write('\x1b[?1h') - - -class cooked_mode(raw_mode): - """ + + +class cooked_mode(raw_mode): + """ The opposide of ``raw_mode``, used when we need cooked mode inside a `raw_mode` block. Used in `CommandLineInterface.run_in_terminal`.:: - - with cooked_mode(stdin): - ''' the pseudo-terminal stdin is now used in cooked mode. ''' - """ + + with cooked_mode(stdin): + ''' the pseudo-terminal stdin is now used in cooked mode. ''' + """ @classmethod def _patch_lflag(cls, attrs): - return attrs | (termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) + return attrs | (termios.ECHO | termios.ICANON | termios.IEXTEN | termios.ISIG) @classmethod def _patch_iflag(cls, attrs): diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_output.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_output.py index b800aaacec..1430943167 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_output.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/vt100_output.py @@ -6,28 +6,28 @@ A lot of thanks, regarding outputting of colors, goes to the Pygments project: everything has been highly optimized.) http://pygments.org/ """ -from __future__ import unicode_literals - +from __future__ import unicode_literals + from prompt_toolkit.filters import to_simple_filter, Condition -from prompt_toolkit.layout.screen import Size -from prompt_toolkit.renderer import Output -from prompt_toolkit.styles import ANSI_COLOR_NAMES - +from prompt_toolkit.layout.screen import Size +from prompt_toolkit.renderer import Output +from prompt_toolkit.styles import ANSI_COLOR_NAMES + from six.moves import range -import array -import errno +import array +import errno import os -import six - -__all__ = ( - 'Vt100_Output', -) - - -FG_ANSI_COLORS = { +import six + +__all__ = ( + 'Vt100_Output', +) + + +FG_ANSI_COLORS = { 'ansidefault': 39, - - # Low intensity. + + # Low intensity. 'ansiblack': 30, 'ansidarkred': 31, 'ansidarkgreen': 32, @@ -36,8 +36,8 @@ FG_ANSI_COLORS = { 'ansipurple': 35, 'ansiteal': 36, 'ansilightgray': 37, - - # High intensity. + + # High intensity. 'ansidarkgray': 90, 'ansired': 91, 'ansigreen': 92, @@ -46,12 +46,12 @@ FG_ANSI_COLORS = { 'ansifuchsia': 95, 'ansiturquoise': 96, 'ansiwhite': 97, -} - -BG_ANSI_COLORS = { +} + +BG_ANSI_COLORS = { 'ansidefault': 49, - - # Low intensity. + + # Low intensity. 'ansiblack': 40, 'ansidarkred': 41, 'ansidarkgreen': 42, @@ -60,8 +60,8 @@ BG_ANSI_COLORS = { 'ansipurple': 45, 'ansiteal': 46, 'ansilightgray': 47, - - # High intensity. + + # High intensity. 'ansidarkgray': 100, 'ansired': 101, 'ansigreen': 102, @@ -70,8 +70,8 @@ BG_ANSI_COLORS = { 'ansifuchsia': 105, 'ansiturquoise': 106, 'ansiwhite': 107, -} - +} + ANSI_COLORS_TO_RGB = { 'ansidefault': (0x00, 0x00, 0x00), # Don't use, 'default' doesn't really have a value. @@ -98,11 +98,11 @@ ANSI_COLORS_TO_RGB = { } -assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) -assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) +assert set(FG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) +assert set(BG_ANSI_COLORS) == set(ANSI_COLOR_NAMES) assert set(ANSI_COLORS_TO_RGB) == set(ANSI_COLOR_NAMES) - - + + def _get_closest_ansi_color(r, g, b, exclude=()): """ Find closest ANSI color. Return it by name. @@ -238,43 +238,43 @@ _16_bg_colors = _16ColorCache(bg=True) _256_colors = _256ColorCache() -class _EscapeCodeCache(dict): - """ - Cache for VT100 escape codes. It maps - (fgcolor, bgcolor, bold, underline, reverse) tuples to VT100 escape sequences. - - :param true_color: When True, use 24bit colors instead of 256 colors. - """ +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, ansi_colors_only=False): - assert isinstance(true_color, bool) - self.true_color = true_color + assert isinstance(true_color, bool) + self.true_color = true_color self.ansi_colors_only = to_simple_filter(ansi_colors_only) - - def __missing__(self, attrs): - fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs - parts = [] - + + def __missing__(self, attrs): + fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs + parts = [] + parts.extend(self._colors_to_code(fgcolor, bgcolor)) - if bold: - parts.append('1') - if italic: - parts.append('3') - if blink: - parts.append('5') - if underline: - parts.append('4') - if reverse: - parts.append('7') - - if parts: - result = '\x1b[0;' + ';'.join(parts) + 'm' - else: - result = '\x1b[0m' - - self[attrs] = result - return result - + 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_name_to_rgb(self, color): " Turn 'ffffff', into (0xff, 0xff, 0xff). " try: @@ -282,35 +282,35 @@ class _EscapeCodeCache(dict): except ValueError: raise else: - r = (rgb >> 16) & 0xff - g = (rgb >> 8) & 0xff - b = rgb & 0xff + r = (rgb >> 16) & 0xff + g = (rgb >> 8) & 0xff + b = rgb & 0xff return r, g, b - + def _colors_to_code(self, fg_color, bg_color): " Return a tuple with the vt100 values that represent this color. " # When requesting ANSI colors only, and both fg/bg color were converted # to ANSI, ensure that the foreground and background color are not the # same. (Unless they were explicitely defined to be the same color.) fg_ansi = [()] - + def get(color, bg): table = BG_ANSI_COLORS if bg else FG_ANSI_COLORS - + if color is None: return () - + # 16 ANSI colors. (Given by name.) elif color in table: return (table[color], ) - + # RGB colors. (Defined as 'ffffff'.) else: try: rgb = self._color_name_to_rgb(color) except ValueError: return () - + # When only 16 colors are supported, use that. if self.ansi_colors_only(): if bg: # Background. @@ -324,7 +324,7 @@ class _EscapeCodeCache(dict): code, name = _16_fg_colors.get_code(rgb) fg_ansi[0] = name return (code, ) - + # True colors. (Only when this feature is enabled.) elif self.true_color: r, g, b = rgb @@ -341,44 +341,44 @@ class _EscapeCodeCache(dict): return map(six.text_type, result) -def _get_size(fileno): - # Thanks to fabric (fabfile.org), and - # http://sqizit.bartletts.id.au/2011/02/14/pseudo-terminals-in-python/ - """ - Get the size of this pseudo terminal. - - :param fileno: stdout.fileno() - :returns: A (rows, cols) tuple. - """ - # Inline imports, because these modules are not available on Windows. - # (This file is used by ConEmuOutput, which is used on Windows.) - import fcntl - import termios - - # Buffer for the C call +def _get_size(fileno): + # Thanks to fabric (fabfile.org), and + # http://sqizit.bartletts.id.au/2011/02/14/pseudo-terminals-in-python/ + """ + Get the size of this pseudo terminal. + + :param fileno: stdout.fileno() + :returns: A (rows, cols) tuple. + """ + # Inline imports, because these modules are not available on Windows. + # (This file is used by ConEmuOutput, which is used on Windows.) + import fcntl + import termios + + # Buffer for the C call buf = array.array(b'h' if six.PY2 else u'h', [0, 0, 0, 0]) - - # Do TIOCGWINSZ (Get) + + # Do TIOCGWINSZ (Get) # Note: We should not pass 'True' as a fourth parameter to 'ioctl'. (True # is the default.) This causes segmentation faults on some systems. # See: https://github.com/jonathanslenders/python-prompt-toolkit/pull/364 fcntl.ioctl(fileno, termios.TIOCGWINSZ, buf) - - # Return rows, cols - return buf[0], buf[1] - - -class Vt100_Output(Output): - """ - :param get_size: A callable which returns the `Size` of the output terminal. + + # Return rows, cols + return buf[0], buf[1] + + +class Vt100_Output(Output): + """ + :param get_size: A callable which returns the `Size` of the output terminal. :param stdout: Any object with has a `write` and `flush` method + an 'encoding' property. - :param true_color: Use 24bit color instead of 256 colors. (Can be a :class:`SimpleFilter`.) + :param true_color: Use 24bit color instead of 256 colors. (Can be a :class:`SimpleFilter`.) When `ansi_colors_only` is set, only 16 colors are used. :param ansi_colors_only: Restrict to 16 ANSI colors only. :param term: The terminal environment variable. (xterm, xterm-256color, linux, ...) :param write_binary: Encode the output before writing it. If `True` (the default), the `stdout` object is supposed to expose an `encoding` attribute. - """ + """ def __init__(self, stdout, get_size, true_color=False, ansi_colors_only=None, term=None, write_binary=True): assert callable(get_size) @@ -388,13 +388,13 @@ class Vt100_Output(Output): if write_binary: assert hasattr(stdout, 'encoding') - self._buffer = [] - self.stdout = stdout + self._buffer = [] + self.stdout = stdout self.write_binary = write_binary - self.get_size = get_size - self.true_color = to_simple_filter(true_color) + self.get_size = get_size + self.true_color = to_simple_filter(true_color) self.term = term or 'xterm' - + # ANSI colors only? if ansi_colors_only is None: # When not given, use the following default. @@ -414,23 +414,23 @@ class Vt100_Output(Output): self._escape_code_cache_true_color = _EscapeCodeCache( true_color=True, ansi_colors_only=ansi_colors_only) - @classmethod + @classmethod def from_pty(cls, stdout, true_color=False, ansi_colors_only=None, term=None): - """ - Create an Output class from a pseudo terminal. - (This will take the dimensions by reading the pseudo - terminal attributes.) - """ + """ + Create an Output class from a pseudo terminal. + (This will take the dimensions by reading the pseudo + terminal attributes.) + """ assert stdout.isatty() - def get_size(): - rows, columns = _get_size(stdout.fileno()) + def get_size(): + rows, columns = _get_size(stdout.fileno()) # If terminal (incorrectly) reports its size as 0, pick a reasonable default. # See https://github.com/ipython/ipython/issues/10071 return Size(rows=(rows or 24), columns=(columns or 80)) - + return cls(stdout, get_size, true_color=true_color, ansi_colors_only=ansi_colors_only, term=term) - + def fileno(self): " Return file descriptor. " return self.stdout.fileno() @@ -439,194 +439,194 @@ class Vt100_Output(Output): " Return encoding used for stdout. " return self.stdout.encoding - def write_raw(self, data): - """ - Write raw data to output. - """ - self._buffer.append(data) - - def write(self, data): - """ - Write text to output. - (Removes vt100 escape codes. -- used for safely writing text.) - """ - self._buffer.append(data.replace('\x1b', '?')) - - def set_title(self, title): - """ - Set terminal title. - """ + def write_raw(self, data): + """ + Write raw data to output. + """ + self._buffer.append(data) + + def write(self, data): + """ + Write text to output. + (Removes vt100 escape codes. -- used for safely writing text.) + """ + self._buffer.append(data.replace('\x1b', '?')) + + def set_title(self, title): + """ + Set terminal title. + """ if self.term not in ('linux', 'eterm-color'): # Not supported by the Linux console. self.write_raw('\x1b]2;%s\x07' % title.replace('\x1b', '').replace('\x07', '')) - - def clear_title(self): - self.set_title('') - - def erase_screen(self): - """ - Erases the screen with the background colour and moves the cursor to - home. - """ - self.write_raw('\x1b[2J') - - def enter_alternate_screen(self): - self.write_raw('\x1b[?1049h\x1b[H') - - def quit_alternate_screen(self): - self.write_raw('\x1b[?1049l') - - def enable_mouse_support(self): - self.write_raw('\x1b[?1000h') - - # Enable urxvt Mouse mode. (For terminals that understand this.) - self.write_raw('\x1b[?1015h') - - # Also enable Xterm SGR mouse mode. (For terminals that understand this.) - self.write_raw('\x1b[?1006h') - - # Note: E.g. lxterminal understands 1000h, but not the urxvt or sgr - # extensions. - - def disable_mouse_support(self): - self.write_raw('\x1b[?1000l') - self.write_raw('\x1b[?1015l') - self.write_raw('\x1b[?1006l') - - def erase_end_of_line(self): - """ - Erases from the current cursor position to the end of the current line. - """ - self.write_raw('\x1b[K') - - def erase_down(self): - """ - Erases the screen from the current line down to the bottom of the - screen. - """ - self.write_raw('\x1b[J') - - def reset_attributes(self): - self.write_raw('\x1b[0m') - - def set_attributes(self, attrs): - """ - Create new style and output. - - :param attrs: `Attrs` instance. - """ + + 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() and not self.ansi_colors_only(): self.write_raw(self._escape_code_cache_true_color[attrs]) - else: + else: self.write_raw(self._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: + + def disable_autowrap(self): + self.write_raw('\x1b[?7l') + + def enable_autowrap(self): + self.write_raw('\x1b[?7h') + + def enable_bracketed_paste(self): + self.write_raw('\x1b[?2004h') + + def disable_bracketed_paste(self): + self.write_raw('\x1b[?2004l') + + def cursor_goto(self, row=0, column=0): + """ Move cursor position. """ + self.write_raw('\x1b[%i;%iH' % (row, column)) + + def cursor_up(self, amount): + if amount == 0: pass - elif amount == 1: - self.write_raw('\x1b[A') - else: - self.write_raw('\x1b[%iA' % amount) - - def cursor_down(self, amount): - if amount == 0: + elif amount == 1: + self.write_raw('\x1b[A') + else: + self.write_raw('\x1b[%iA' % amount) + + def cursor_down(self, amount): + if amount == 0: pass - elif amount == 1: - # Note: Not the same as '\n', '\n' can cause the window content to - # scroll. - self.write_raw('\x1b[B') - else: - self.write_raw('\x1b[%iB' % amount) - - def cursor_forward(self, amount): - if amount == 0: + elif amount == 1: + # Note: Not the same as '\n', '\n' can cause the window content to + # scroll. + self.write_raw('\x1b[B') + else: + self.write_raw('\x1b[%iB' % amount) + + def cursor_forward(self, amount): + if amount == 0: pass - elif amount == 1: - self.write_raw('\x1b[C') - else: - self.write_raw('\x1b[%iC' % amount) - - def cursor_backward(self, amount): - if amount == 0: + elif amount == 1: + self.write_raw('\x1b[C') + else: + self.write_raw('\x1b[%iC' % amount) + + def cursor_backward(self, amount): + if amount == 0: pass - elif amount == 1: - self.write_raw('\b') # '\x1b[D' - else: - self.write_raw('\x1b[%iD' % amount) - - def hide_cursor(self): - self.write_raw('\x1b[?25l') - - def show_cursor(self): - self.write_raw('\x1b[?12l\x1b[?25h') # Stop blinking cursor and show. - - def flush(self): - """ - Write to output stream and flush. - """ - if not self._buffer: - return - - data = ''.join(self._buffer) - - try: - # (We try to encode ourself, because that way we can replace - # characters that don't exist in the character set, avoiding - # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.) - # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968' - # for sys.stdout.encoding in xterm. + elif amount == 1: + self.write_raw('\b') # '\x1b[D' + else: + self.write_raw('\x1b[%iD' % amount) + + def hide_cursor(self): + self.write_raw('\x1b[?25l') + + def show_cursor(self): + self.write_raw('\x1b[?12l\x1b[?25h') # Stop blinking cursor and show. + + def flush(self): + """ + Write to output stream and flush. + """ + if not self._buffer: + return + + data = ''.join(self._buffer) + + try: + # (We try to encode ourself, because that way we can replace + # characters that don't exist in the character set, avoiding + # UnicodeEncodeError crashes. E.g. u'\xb7' does not appear in 'ascii'.) + # My Arch Linux installation of july 2015 reported 'ANSI_X3.4-1968' + # for sys.stdout.encoding in xterm. if self.write_binary: if hasattr(self.stdout, 'buffer'): out = self.stdout.buffer # Py3. else: out = self.stdout - out.write(data.encode(self.stdout.encoding or 'utf-8', 'replace')) - else: - self.stdout.write(data) - - self.stdout.flush() - except IOError as e: - if e.args and e.args[0] == errno.EINTR: - # Interrupted system call. Can 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() + 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() diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_input.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_input.py index 410e5fa517..708df868f9 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_input.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_input.py @@ -1,113 +1,113 @@ from __future__ import unicode_literals -from ctypes import windll, pointer +from ctypes import windll, pointer from ctypes.wintypes import DWORD, HANDLE from six.moves import range - -from prompt_toolkit.key_binding.input_processor import KeyPress -from prompt_toolkit.keys import Keys + +from prompt_toolkit.key_binding.input_processor import KeyPress +from prompt_toolkit.keys import Keys from prompt_toolkit.mouse_events import MouseEventType -from prompt_toolkit.win32_types import EventTypes, KEY_EVENT_RECORD, MOUSE_EVENT_RECORD, INPUT_RECORD, STD_INPUT_HANDLE - +from prompt_toolkit.win32_types import EventTypes, KEY_EVENT_RECORD, MOUSE_EVENT_RECORD, INPUT_RECORD, STD_INPUT_HANDLE + import msvcrt import os import sys import six -__all__ = ( - 'ConsoleInputReader', - 'raw_mode', - 'cooked_mode' -) - - -class ConsoleInputReader(object): +__all__ = ( + 'ConsoleInputReader', + 'raw_mode', + 'cooked_mode' +) + + +class ConsoleInputReader(object): """ :param recognize_paste: When True, try to discover paste actions and turn the event into a BracketedPaste. """ - # Keys with character data. - mappings = { - b'\x1b': Keys.Escape, - - b'\x00': Keys.ControlSpace, # Control-Space (Also for Ctrl-@) - b'\x01': Keys.ControlA, # Control-A (home) - b'\x02': Keys.ControlB, # Control-B (emacs cursor left) - b'\x03': Keys.ControlC, # Control-C (interrupt) - b'\x04': Keys.ControlD, # Control-D (exit) - b'\x05': Keys.ControlE, # Contrel-E (end) - b'\x06': Keys.ControlF, # Control-F (cursor forward) - b'\x07': Keys.ControlG, # Control-G - b'\x08': Keys.ControlH, # Control-H (8) (Identical to '\b') - b'\x09': Keys.ControlI, # Control-I (9) (Identical to '\t') - b'\x0a': Keys.ControlJ, # Control-J (10) (Identical to '\n') - b'\x0b': Keys.ControlK, # Control-K (delete until end of line; vertical tab) - b'\x0c': Keys.ControlL, # Control-L (clear; form feed) - b'\x0d': Keys.ControlJ, # Control-J NOTE: Windows sends \r instead of - # \n when pressing enter. We turn it into \n - # to be compatible with other platforms. - b'\x0e': Keys.ControlN, # Control-N (14) (history forward) - b'\x0f': Keys.ControlO, # Control-O (15) - b'\x10': Keys.ControlP, # Control-P (16) (history back) - b'\x11': Keys.ControlQ, # Control-Q - b'\x12': Keys.ControlR, # Control-R (18) (reverse search) - b'\x13': Keys.ControlS, # Control-S (19) (forward search) - b'\x14': Keys.ControlT, # Control-T - b'\x15': Keys.ControlU, # Control-U - b'\x16': Keys.ControlV, # Control-V - b'\x17': Keys.ControlW, # Control-W - b'\x18': Keys.ControlX, # Control-X - b'\x19': Keys.ControlY, # Control-Y (25) - b'\x1a': Keys.ControlZ, # Control-Z - - b'\x1c': Keys.ControlBackslash, # Both Control-\ and Ctrl-| - b'\x1d': Keys.ControlSquareClose, # Control-] - b'\x1e': Keys.ControlCircumflex, # Control-^ - b'\x1f': Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hypen.) - b'\x7f': Keys.Backspace, # (127) Backspace - } - - # Keys that don't carry character data. - keycodes = { - # Home/End - 33: Keys.PageUp, - 34: Keys.PageDown, - 35: Keys.End, - 36: Keys.Home, - - # Arrows - 37: Keys.Left, - 38: Keys.Up, - 39: Keys.Right, - 40: Keys.Down, - - 45: Keys.Insert, - 46: Keys.Delete, - - # F-keys. - 112: Keys.F1, - 113: Keys.F2, - 114: Keys.F3, - 115: Keys.F4, - 116: Keys.F5, - 117: Keys.F6, - 118: Keys.F7, - 119: Keys.F8, - 120: Keys.F9, - 121: Keys.F10, - 122: Keys.F11, - 123: Keys.F12, - } - - LEFT_ALT_PRESSED = 0x0002 - RIGHT_ALT_PRESSED = 0x0001 - SHIFT_PRESSED = 0x0010 - LEFT_CTRL_PRESSED = 0x0008 - RIGHT_CTRL_PRESSED = 0x0004 - + # Keys with character data. + mappings = { + b'\x1b': Keys.Escape, + + b'\x00': Keys.ControlSpace, # Control-Space (Also for Ctrl-@) + b'\x01': Keys.ControlA, # Control-A (home) + b'\x02': Keys.ControlB, # Control-B (emacs cursor left) + b'\x03': Keys.ControlC, # Control-C (interrupt) + b'\x04': Keys.ControlD, # Control-D (exit) + b'\x05': Keys.ControlE, # Contrel-E (end) + b'\x06': Keys.ControlF, # Control-F (cursor forward) + b'\x07': Keys.ControlG, # Control-G + b'\x08': Keys.ControlH, # Control-H (8) (Identical to '\b') + b'\x09': Keys.ControlI, # Control-I (9) (Identical to '\t') + b'\x0a': Keys.ControlJ, # Control-J (10) (Identical to '\n') + b'\x0b': Keys.ControlK, # Control-K (delete until end of line; vertical tab) + b'\x0c': Keys.ControlL, # Control-L (clear; form feed) + b'\x0d': Keys.ControlJ, # Control-J NOTE: Windows sends \r instead of + # \n when pressing enter. We turn it into \n + # to be compatible with other platforms. + b'\x0e': Keys.ControlN, # Control-N (14) (history forward) + b'\x0f': Keys.ControlO, # Control-O (15) + b'\x10': Keys.ControlP, # Control-P (16) (history back) + b'\x11': Keys.ControlQ, # Control-Q + b'\x12': Keys.ControlR, # Control-R (18) (reverse search) + b'\x13': Keys.ControlS, # Control-S (19) (forward search) + b'\x14': Keys.ControlT, # Control-T + b'\x15': Keys.ControlU, # Control-U + b'\x16': Keys.ControlV, # Control-V + b'\x17': Keys.ControlW, # Control-W + b'\x18': Keys.ControlX, # Control-X + b'\x19': Keys.ControlY, # Control-Y (25) + b'\x1a': Keys.ControlZ, # Control-Z + + b'\x1c': Keys.ControlBackslash, # Both Control-\ and Ctrl-| + b'\x1d': Keys.ControlSquareClose, # Control-] + b'\x1e': Keys.ControlCircumflex, # Control-^ + b'\x1f': Keys.ControlUnderscore, # Control-underscore (Also for Ctrl-hypen.) + b'\x7f': Keys.Backspace, # (127) Backspace + } + + # Keys that don't carry character data. + keycodes = { + # Home/End + 33: Keys.PageUp, + 34: Keys.PageDown, + 35: Keys.End, + 36: Keys.Home, + + # Arrows + 37: Keys.Left, + 38: Keys.Up, + 39: Keys.Right, + 40: Keys.Down, + + 45: Keys.Insert, + 46: Keys.Delete, + + # F-keys. + 112: Keys.F1, + 113: Keys.F2, + 114: Keys.F3, + 115: Keys.F4, + 116: Keys.F5, + 117: Keys.F6, + 118: Keys.F7, + 119: Keys.F8, + 120: Keys.F9, + 121: Keys.F10, + 122: Keys.F11, + 123: Keys.F12, + } + + LEFT_ALT_PRESSED = 0x0002 + RIGHT_ALT_PRESSED = 0x0001 + SHIFT_PRESSED = 0x0010 + LEFT_CTRL_PRESSED = 0x0008 + RIGHT_CTRL_PRESSED = 0x0004 + def __init__(self, recognize_paste=True): self._fdcon = None self.recognize_paste = recognize_paste - + # When stdin is a tty, use that handle, otherwise, create a handle from # CONIN$. if sys.stdin.isatty(): @@ -121,23 +121,23 @@ class ConsoleInputReader(object): if self._fdcon is not None: os.close(self._fdcon) - def read(self): - """ + def read(self): + """ Return a list of `KeyPress` instances. It won't return anything when there was nothing to read. (This function doesn't block.) - - http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx - """ + + http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx + """ max_count = 2048 # Max events to read at the same time. - - read = DWORD(0) - arrtype = INPUT_RECORD * max_count - input_records = arrtype() - - # Get next batch of input event. + + read = DWORD(0) + arrtype = INPUT_RECORD * max_count + input_records = arrtype() + + # Get next batch of input event. windll.kernel32.ReadConsoleInputW( self.handle, pointer(input_records), max_count, pointer(read)) - + # First, get all the keys from the input buffer, in order to determine # whether we should consider this a paste event or not. all_keys = list(self._get_keys(read, input_records)) @@ -168,26 +168,26 @@ class ConsoleInputReader(object): """ Generator that yields `KeyPress` objects from the input records. """ - for i in range(read.value): - ir = input_records[i] - - # Get the right EventType from the EVENT_RECORD. - # (For some reason the Windows console application 'cmder' - # [http://gooseberrycreative.com/cmder/] can return '0' for - # ir.EventType. -- Just ignore that.) - if ir.EventType in EventTypes: - ev = getattr(ir.Event, EventTypes[ir.EventType]) - - # Process if this is a key event. (We also have mouse, menu and - # focus events.) - if type(ev) == KEY_EVENT_RECORD and ev.KeyDown: + for i in range(read.value): + ir = input_records[i] + + # Get the right EventType from the EVENT_RECORD. + # (For some reason the Windows console application 'cmder' + # [http://gooseberrycreative.com/cmder/] can return '0' for + # ir.EventType. -- Just ignore that.) + if ir.EventType in EventTypes: + ev = getattr(ir.Event, EventTypes[ir.EventType]) + + # Process if this is a key event. (We also have mouse, menu and + # focus events.) + if type(ev) == KEY_EVENT_RECORD and ev.KeyDown: for key_press in self._event_to_key_presses(ev): yield key_press - - elif type(ev) == MOUSE_EVENT_RECORD: + + elif type(ev) == MOUSE_EVENT_RECORD: for key_press in self._handle_mouse(ev): yield key_press - + @staticmethod def _is_paste(keys): """ @@ -201,7 +201,7 @@ class ConsoleInputReader(object): # other character. text_count = 0 newline_count = 0 - + for k in keys: if isinstance(k.key, six.text_type): text_count += 1 @@ -210,155 +210,155 @@ class ConsoleInputReader(object): return newline_count >= 1 and text_count > 1 - def _event_to_key_presses(self, ev): - """ - For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. - """ - assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown - - result = None - - u_char = ev.uChar.UnicodeChar + def _event_to_key_presses(self, ev): + """ + For this `KEY_EVENT_RECORD`, return a list of `KeyPress` instances. + """ + assert type(ev) == KEY_EVENT_RECORD and ev.KeyDown + + result = None + + u_char = ev.uChar.UnicodeChar ascii_char = u_char.encode('utf-8') - + # NOTE: We don't use `ev.uChar.AsciiChar`. That appears to be latin-1 # encoded. See also: # https://github.com/ipython/ipython/issues/10004 # https://github.com/jonathanslenders/python-prompt-toolkit/issues/389 - if u_char == '\x00': - if ev.VirtualKeyCode in self.keycodes: - result = KeyPress(self.keycodes[ev.VirtualKeyCode], '') - else: - if ascii_char in self.mappings: + if u_char == '\x00': + if ev.VirtualKeyCode in self.keycodes: + result = KeyPress(self.keycodes[ev.VirtualKeyCode], '') + else: + if ascii_char in self.mappings: if self.mappings[ascii_char] == Keys.ControlJ: u_char = '\n' # Windows sends \n, turn into \r for unix compatibility. - result = KeyPress(self.mappings[ascii_char], u_char) - else: - result = KeyPress(u_char, u_char) - - # Correctly handle Control-Arrow keys. - if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or - ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result: - if result.key == Keys.Left: - result.key = Keys.ControlLeft - - if result.key == Keys.Right: - result.key = Keys.ControlRight - - if result.key == Keys.Up: - result.key = Keys.ControlUp - - if result.key == Keys.Down: - result.key = Keys.ControlDown - - # Turn 'Tab' into 'BackTab' when shift was pressed. - if ev.ControlKeyState & self.SHIFT_PRESSED and result: - if result.key == Keys.Tab: - result.key = Keys.BackTab - - # Turn 'Space' into 'ControlSpace' when control was pressed. - if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or - ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and result.data == ' ': - result = KeyPress(Keys.ControlSpace, ' ') - - # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot - # detect this combination. But it's really practical on Windows.) - if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or - ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and \ - result.key == Keys.ControlJ: - return [KeyPress(Keys.Escape, ''), result] - - # Return result. If alt was pressed, prefix the result with an - # 'Escape' key, just like unix VT100 terminals do. - - # NOTE: Only replace the left alt with escape. The right alt key often - # acts as altgr and is used in many non US keyboard layouts for - # typing some special characters, like a backslash. We don't want - # all backslashes to be prefixed with escape. (Esc-\ has a - # meaning in E-macs, for instance.) - if result: - meta_pressed = ev.ControlKeyState & self.LEFT_ALT_PRESSED - - if meta_pressed: - return [KeyPress(Keys.Escape, ''), result] - else: - return [result] - - else: - return [] - - def _handle_mouse(self, ev): - """ - Handle mouse events. Return a list of KeyPress instances. - """ - FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 - - result = [] - - # Check event type. - if ev.ButtonState == FROM_LEFT_1ST_BUTTON_PRESSED: - # On a key press, generate both the mouse down and up event. + result = KeyPress(self.mappings[ascii_char], u_char) + else: + result = KeyPress(u_char, u_char) + + # Correctly handle Control-Arrow keys. + if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or + ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result: + if result.key == Keys.Left: + result.key = Keys.ControlLeft + + if result.key == Keys.Right: + result.key = Keys.ControlRight + + if result.key == Keys.Up: + result.key = Keys.ControlUp + + if result.key == Keys.Down: + result.key = Keys.ControlDown + + # Turn 'Tab' into 'BackTab' when shift was pressed. + if ev.ControlKeyState & self.SHIFT_PRESSED and result: + if result.key == Keys.Tab: + result.key = Keys.BackTab + + # Turn 'Space' into 'ControlSpace' when control was pressed. + if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or + ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and result.data == ' ': + result = KeyPress(Keys.ControlSpace, ' ') + + # Turn Control-Enter into META-Enter. (On a vt100 terminal, we cannot + # detect this combination. But it's really practical on Windows.) + if (ev.ControlKeyState & self.LEFT_CTRL_PRESSED or + ev.ControlKeyState & self.RIGHT_CTRL_PRESSED) and result and \ + result.key == Keys.ControlJ: + return [KeyPress(Keys.Escape, ''), result] + + # Return result. If alt was pressed, prefix the result with an + # 'Escape' key, just like unix VT100 terminals do. + + # NOTE: Only replace the left alt with escape. The right alt key often + # acts as altgr and is used in many non US keyboard layouts for + # typing some special characters, like a backslash. We don't want + # all backslashes to be prefixed with escape. (Esc-\ has a + # meaning in E-macs, for instance.) + if result: + meta_pressed = ev.ControlKeyState & self.LEFT_ALT_PRESSED + + if meta_pressed: + return [KeyPress(Keys.Escape, ''), result] + else: + return [result] + + else: + return [] + + def _handle_mouse(self, ev): + """ + Handle mouse events. Return a list of KeyPress instances. + """ + FROM_LEFT_1ST_BUTTON_PRESSED = 0x1 + + result = [] + + # Check event type. + if ev.ButtonState == FROM_LEFT_1ST_BUTTON_PRESSED: + # On a key press, generate both the mouse down and up event. for event_type in [MouseEventType.MOUSE_DOWN, MouseEventType.MOUSE_UP]: - data = ';'.join([ - event_type, - str(ev.MousePosition.X), - str(ev.MousePosition.Y) - ]) - result.append(KeyPress(Keys.WindowsMouseEvent, data)) - - return result - - -class raw_mode(object): - """ - :: - - with raw_mode(stdin): - ''' the windows terminal is now in 'raw' mode. ''' - - The ``fileno`` attribute is ignored. This is to be compatble with the - `raw_input` method of `.vt100_input`. - """ - def __init__(self, fileno=None): + data = ';'.join([ + event_type, + str(ev.MousePosition.X), + str(ev.MousePosition.Y) + ]) + result.append(KeyPress(Keys.WindowsMouseEvent, data)) + + return result + + +class raw_mode(object): + """ + :: + + with raw_mode(stdin): + ''' the windows terminal is now in 'raw' mode. ''' + + The ``fileno`` attribute is ignored. This is to be compatble with the + `raw_input` method of `.vt100_input`. + """ + def __init__(self, fileno=None): self.handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) - - def __enter__(self): - # Remember original mode. - original_mode = DWORD() - windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode)) - self.original_mode = original_mode - - self._patch() - - def _patch(self): - # Set raw - ENABLE_ECHO_INPUT = 0x0004 - ENABLE_LINE_INPUT = 0x0002 - ENABLE_PROCESSED_INPUT = 0x0001 - - windll.kernel32.SetConsoleMode( - self.handle, self.original_mode.value & - ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) - - def __exit__(self, *a, **kw): - # Restore original mode - windll.kernel32.SetConsoleMode(self.handle, self.original_mode) - - -class cooked_mode(raw_mode): - """ - :: - - with cooked_mode(stdin): - ''' the pseudo-terminal stdin is now used in raw mode ''' - """ - def _patch(self): - # Set cooked. - ENABLE_ECHO_INPUT = 0x0004 - ENABLE_LINE_INPUT = 0x0002 - ENABLE_PROCESSED_INPUT = 0x0001 - - windll.kernel32.SetConsoleMode( - self.handle, self.original_mode.value | - (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) + + def __enter__(self): + # Remember original mode. + original_mode = DWORD() + windll.kernel32.GetConsoleMode(self.handle, pointer(original_mode)) + self.original_mode = original_mode + + self._patch() + + def _patch(self): + # Set raw + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_PROCESSED_INPUT = 0x0001 + + windll.kernel32.SetConsoleMode( + self.handle, self.original_mode.value & + ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) + + def __exit__(self, *a, **kw): + # Restore original mode + windll.kernel32.SetConsoleMode(self.handle, self.original_mode) + + +class cooked_mode(raw_mode): + """ + :: + + with cooked_mode(stdin): + ''' the pseudo-terminal stdin is now used in raw mode ''' + """ + def _patch(self): + # Set cooked. + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_PROCESSED_INPUT = 0x0001 + + windll.kernel32.SetConsoleMode( + self.handle, self.original_mode.value | + (ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT)) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_output.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_output.py index d4dddbab42..4ed5beb8aa 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_output.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/terminal/win32_output.py @@ -1,45 +1,45 @@ -from __future__ import unicode_literals - -from ctypes import windll, byref, ArgumentError, c_char, c_long, c_ulong, c_uint, pointer +from __future__ import unicode_literals + +from ctypes import windll, byref, ArgumentError, c_char, c_long, c_ulong, c_uint, pointer from ctypes.wintypes import DWORD, HANDLE - -from prompt_toolkit.renderer import Output -from prompt_toolkit.styles import ANSI_COLOR_NAMES -from prompt_toolkit.win32_types import CONSOLE_SCREEN_BUFFER_INFO, STD_OUTPUT_HANDLE, STD_INPUT_HANDLE, COORD, SMALL_RECT - + +from prompt_toolkit.renderer import Output +from prompt_toolkit.styles import ANSI_COLOR_NAMES +from prompt_toolkit.win32_types import CONSOLE_SCREEN_BUFFER_INFO, STD_OUTPUT_HANDLE, STD_INPUT_HANDLE, COORD, SMALL_RECT + import os -import six - -__all__ = ( - 'Win32Output', -) - - -def _coord_byval(coord): - """ - Turns a COORD object into a c_long. - This will cause it to be passed by value instead of by reference. (That is what I think at least.) - - When runing ``ptipython`` is run (only with IPython), we often got the following error:: - - Error in 'SetConsoleCursorPosition'. - ArgumentError("argument 2: <class 'TypeError'>: wrong type",) - argument 2: <class 'TypeError'>: 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' - - +import six + +__all__ = ( + 'Win32Output', +) + + +def _coord_byval(coord): + """ + Turns a COORD object into a c_long. + This will cause it to be passed by value instead of by reference. (That is what I think at least.) + + When runing ``ptipython`` is run (only with IPython), we often got the following error:: + + Error in 'SetConsoleCursorPosition'. + ArgumentError("argument 2: <class 'TypeError'>: wrong type",) + argument 2: <class 'TypeError'>: wrong type + + It was solved by turning ``COORD`` parameters into a ``c_long`` like this. + + More info: http://msdn.microsoft.com/en-us/library/windows/desktop/ms686025(v=vs.85).aspx + """ + return c_long(coord.Y * 0x10000 | coord.X & 0xFFFF) + + +#: If True: write the output of the renderer also to the following file. This +#: is very useful for debugging. (e.g.: to see that we don't write more bytes +#: than required.) +_DEBUG_RENDER_OUTPUT = False +_DEBUG_RENDER_OUTPUT_FILENAME = r'prompt-toolkit-windows-output.log' + + class NoConsoleScreenBufferError(Exception): """ Raised when the application is not running inside a Windows Console, but @@ -60,29 +60,29 @@ class NoConsoleScreenBufferError(Exception): super(NoConsoleScreenBufferError, self).__init__(message) -class Win32Output(Output): - """ - I/O abstraction for rendering to Windows consoles. - (cmd.exe and similar.) - """ - def __init__(self, stdout, use_complete_width=False): - self.use_complete_width = use_complete_width - - self._buffer = [] - self.stdout = stdout +class Win32Output(Output): + """ + I/O abstraction for rendering to Windows consoles. + (cmd.exe and similar.) + """ + def __init__(self, stdout, use_complete_width=False): + self.use_complete_width = use_complete_width + + self._buffer = [] + self.stdout = stdout self.hconsole = HANDLE(windll.kernel32.GetStdHandle(STD_OUTPUT_HANDLE)) - - self._in_alternate_screen = False - - self.color_lookup_table = ColorLookupTable() - + + self._in_alternate_screen = False + + self.color_lookup_table = ColorLookupTable() + # Remember the default console colors. info = self.get_win32_screen_buffer_info() self.default_attrs = info.wAttributes if info else 15 - if _DEBUG_RENDER_OUTPUT: - self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, 'ab') - + if _DEBUG_RENDER_OUTPUT: + self.LOG = open(_DEBUG_RENDER_OUTPUT_FILENAME, 'ab') + def fileno(self): " Return file descriptor. " return self.stdout.fileno() @@ -91,56 +91,56 @@ class Win32Output(Output): " Return encoding used for stdout. " return self.stdout.encoding - 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. - """ + 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. + """ # NOTE: We don't call the `GetConsoleScreenBufferInfo` API through # `self._winapi`. Doing so causes Python to crash on certain 64bit # Python versions. (Reproduced with 64bit Python 2.7.6, on Windows @@ -160,74 +160,74 @@ class Win32Output(Output): # - https://github.com/jonathanslenders/python-prompt-toolkit/issues/86 self.flush() - sbinfo = CONSOLE_SCREEN_BUFFER_INFO() + sbinfo = CONSOLE_SCREEN_BUFFER_INFO() success = windll.kernel32.GetConsoleScreenBufferInfo(self.hconsole, byref(sbinfo)) # success = self._winapi(windll.kernel32.GetConsoleScreenBufferInfo, # self.hconsole, byref(sbinfo)) - if success: - return sbinfo + if success: + return sbinfo else: raise NoConsoleScreenBufferError - - def set_title(self, title): - """ - Set terminal title. - """ - assert isinstance(title, six.text_type) - self._winapi(windll.kernel32.SetConsoleTitleW, title) - - def clear_title(self): - self._winapi(windll.kernel32.SetConsoleTitleW, '') - - def erase_screen(self): - start = COORD(0, 0) - sbinfo = self.get_win32_screen_buffer_info() - length = sbinfo.dwSize.X * sbinfo.dwSize.Y - - self.cursor_goto(row=0, column=0) - self._erase(start, length) - - def erase_down(self): - sbinfo = self.get_win32_screen_buffer_info() - size = sbinfo.dwSize - - start = sbinfo.dwCursorPosition - length = ((size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y)) - - self._erase(start, length) - - def erase_end_of_line(self): - """ - """ - sbinfo = self.get_win32_screen_buffer_info() - start = sbinfo.dwCursorPosition - length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X - - self._erase(start, length) - - def _erase(self, start, length): - chars_written = c_ulong() - - self._winapi(windll.kernel32.FillConsoleOutputCharacterA, - self.hconsole, c_char(b' '), DWORD(length), _coord_byval(start), - byref(chars_written)) - - # Reset attributes. - sbinfo = self.get_win32_screen_buffer_info() - self._winapi(windll.kernel32.FillConsoleOutputAttribute, - self.hconsole, sbinfo.wAttributes, length, _coord_byval(start), - byref(chars_written)) - - def reset_attributes(self): + + def set_title(self, title): + """ + Set terminal title. + """ + assert isinstance(title, six.text_type) + self._winapi(windll.kernel32.SetConsoleTitleW, title) + + def clear_title(self): + self._winapi(windll.kernel32.SetConsoleTitleW, '') + + def erase_screen(self): + start = COORD(0, 0) + sbinfo = self.get_win32_screen_buffer_info() + length = sbinfo.dwSize.X * sbinfo.dwSize.Y + + self.cursor_goto(row=0, column=0) + self._erase(start, length) + + def erase_down(self): + sbinfo = self.get_win32_screen_buffer_info() + size = sbinfo.dwSize + + start = sbinfo.dwCursorPosition + length = ((size.X - size.X) + size.X * (size.Y - sbinfo.dwCursorPosition.Y)) + + self._erase(start, length) + + def erase_end_of_line(self): + """ + """ + sbinfo = self.get_win32_screen_buffer_info() + start = sbinfo.dwCursorPosition + length = sbinfo.dwSize.X - sbinfo.dwCursorPosition.X + + self._erase(start, length) + + def _erase(self, start, length): + chars_written = c_ulong() + + self._winapi(windll.kernel32.FillConsoleOutputCharacterA, + self.hconsole, c_char(b' '), DWORD(length), _coord_byval(start), + byref(chars_written)) + + # Reset attributes. + sbinfo = self.get_win32_screen_buffer_info() + self._winapi(windll.kernel32.FillConsoleOutputAttribute, + self.hconsole, sbinfo.wAttributes, length, _coord_byval(start), + byref(chars_written)) + + def reset_attributes(self): " Reset the console foreground/background color. " self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, self.default_attrs) - - def set_attributes(self, attrs): - fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs - + + def set_attributes(self, attrs): + fgcolor, bgcolor, bold, underline, italic, blink, reverse = attrs + # Start from the default attributes. attrs = self.default_attrs @@ -242,306 +242,306 @@ class Win32Output(Output): attrs |= self.color_lookup_table.lookup_bg_color(bgcolor) # Reverse: swap these four bits groups. - if reverse: + if reverse: attrs = (attrs & ~0xff) | ((attrs & 0xf) << 4) | ((attrs & 0xf0) >> 4) - + self._winapi(windll.kernel32.SetConsoleTextAttribute, self.hconsole, attrs) - - def disable_autowrap(self): - # Not supported by Windows. - pass - - def enable_autowrap(self): - # Not supported by Windows. - pass - - def cursor_goto(self, row=0, column=0): - pos = COORD(x=column, y=row) - self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) - - def cursor_up(self, amount): - sr = self.get_win32_screen_buffer_info().dwCursorPosition - pos = COORD(sr.X, sr.Y - amount) - self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) - - def cursor_down(self, amount): - self.cursor_up(-amount) - - def cursor_forward(self, amount): - sr = self.get_win32_screen_buffer_info().dwCursorPosition -# assert sr.X + amount >= 0, 'Negative cursor position: x=%r amount=%r' % (sr.X, amount) - - pos = COORD(max(0, sr.X + amount), sr.Y) - self._winapi(windll.kernel32.SetConsoleCursorPosition, self.hconsole, _coord_byval(pos)) - - def cursor_backward(self, amount): - self.cursor_forward(-amount) - - def flush(self): - """ - Write to output stream and flush. - """ - if not self._buffer: - # Only flush stdout buffer. (It could be that Python still has - # something in its buffer. -- We want to be sure to print that in - # the correct color.) - self.stdout.flush() - return - - data = ''.join(self._buffer) - - if _DEBUG_RENDER_OUTPUT: - self.LOG.write(('%r' % data).encode('utf-8') + b'\n') - self.LOG.flush() - - # Print characters one by one. This appears to be the best 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 + + 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 if 0 < sr.Bottom - cursor_pos.Y < win_height - 1: # no vertical scroll if cursor already on the screen result.Bottom = sr.Bottom else: result.Bottom = max(win_height, cursor_pos.Y) - result.Top = result.Bottom - win_height - - # Scroll API - self._winapi(windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result)) - - def enter_alternate_screen(self): - """ - Go to alternate screen buffer. - """ - if not self._in_alternate_screen: - GENERIC_READ = 0x80000000 - GENERIC_WRITE = 0x40000000 - - # Create a new console buffer and activate that one. + result.Top = result.Bottom - win_height + + # Scroll API + self._winapi(windll.kernel32.SetConsoleWindowInfo, self.hconsole, True, byref(result)) + + def enter_alternate_screen(self): + """ + Go to alternate screen buffer. + """ + if not self._in_alternate_screen: + GENERIC_READ = 0x80000000 + GENERIC_WRITE = 0x40000000 + + # Create a new console buffer and activate that one. handle = HANDLE(self._winapi(windll.kernel32.CreateConsoleScreenBuffer, GENERIC_READ|GENERIC_WRITE, DWORD(0), None, DWORD(1), None)) - - self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle) - self.hconsole = handle - self._in_alternate_screen = True - - def quit_alternate_screen(self): - """ - Make stdout again the active buffer. - """ - if self._in_alternate_screen: + + self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, handle) + self.hconsole = handle + self._in_alternate_screen = True + + def quit_alternate_screen(self): + """ + Make stdout again the active buffer. + """ + if self._in_alternate_screen: stdout = HANDLE(self._winapi(windll.kernel32.GetStdHandle, STD_OUTPUT_HANDLE)) - self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout) - self._winapi(windll.kernel32.CloseHandle, self.hconsole) - self.hconsole = stdout - self._in_alternate_screen = False - - def enable_mouse_support(self): - ENABLE_MOUSE_INPUT = 0x10 + self._winapi(windll.kernel32.SetConsoleActiveScreenBuffer, stdout) + self._winapi(windll.kernel32.CloseHandle, self.hconsole) + self.hconsole = stdout + self._in_alternate_screen = False + + def enable_mouse_support(self): + ENABLE_MOUSE_INPUT = 0x10 handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) - - original_mode = DWORD() - self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) - self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value | ENABLE_MOUSE_INPUT) - - def disable_mouse_support(self): - ENABLE_MOUSE_INPUT = 0x10 + + original_mode = DWORD() + self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) + self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value | ENABLE_MOUSE_INPUT) + + def disable_mouse_support(self): + ENABLE_MOUSE_INPUT = 0x10 handle = HANDLE(windll.kernel32.GetStdHandle(STD_INPUT_HANDLE)) - - original_mode = DWORD() - self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) - self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value & ~ ENABLE_MOUSE_INPUT) - - def hide_cursor(self): - pass - - def show_cursor(self): - pass - - @classmethod - def win32_refresh_window(cls): - """ - Call win32 API to refresh the whole Window. - - This is sometimes necessary when the application paints background - for completion menus. When the menu disappears, it leaves traces due - to a bug in the Windows Console. Sending a repaint request solves it. - """ - # Get console handle + + original_mode = DWORD() + self._winapi(windll.kernel32.GetConsoleMode, handle, pointer(original_mode)) + self._winapi(windll.kernel32.SetConsoleMode, handle, original_mode.value & ~ ENABLE_MOUSE_INPUT) + + def hide_cursor(self): + pass + + def show_cursor(self): + pass + + @classmethod + def win32_refresh_window(cls): + """ + Call win32 API to refresh the whole Window. + + This is sometimes necessary when the application paints background + for completion menus. When the menu disappears, it leaves traces due + to a bug in the Windows Console. Sending a repaint request solves it. + """ + # Get console handle handle = HANDLE(windll.kernel32.GetConsoleWindow()) - - RDW_INVALIDATE = 0x0001 - windll.user32.RedrawWindow(handle, None, None, c_uint(RDW_INVALIDATE)) - - -class FOREGROUND_COLOR: - BLACK = 0x0000 - BLUE = 0x0001 - GREEN = 0x0002 - CYAN = 0x0003 - RED = 0x0004 - MAGENTA = 0x0005 - YELLOW = 0x0006 - GRAY = 0x0007 - INTENSITY = 0x0008 # Foreground color is intensified. - - -class 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 { + + 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 { 'ansidefault': color_cls.BLACK, 'ansiblack': color_cls.BLACK, 'ansidarkgray': color_cls.BLACK | color_cls.INTENSITY, 'ansilightgray': color_cls.GRAY, 'ansiwhite': color_cls.GRAY | color_cls.INTENSITY, - # Low intensity. + # Low intensity. 'ansidarkred': color_cls.RED, 'ansidarkgreen': color_cls.GREEN, 'ansibrown': color_cls.YELLOW, 'ansidarkblue': color_cls.BLUE, 'ansipurple': color_cls.MAGENTA, 'ansiteal': color_cls.CYAN, - - # High intensity. + + # High intensity. 'ansired': color_cls.RED | color_cls.INTENSITY, 'ansigreen': color_cls.GREEN | color_cls.INTENSITY, 'ansiyellow': color_cls.YELLOW | color_cls.INTENSITY, 'ansiblue': color_cls.BLUE | color_cls.INTENSITY, 'ansifuchsia': color_cls.MAGENTA | color_cls.INTENSITY, 'ansiturquoise': 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), - + } + +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), + (0x44, 0x44, 0x44, FG.BLACK | FG.INTENSITY, BG.BLACK | BG.INTENSITY), - (0xff, 0xff, 0xff, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY), - ] - - def _closest_color(self, r, g, b): - distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) - fg_match = 0 - bg_match = 0 - - for r_, g_, b_, fg_, bg_ in self._win32_colors: - rd = r - r_ - gd = g - g_ - bd = b - b_ - - d = rd * rd + gd * gd + bd * bd - - if d < distance: - fg_match = fg_ - bg_match = bg_ - distance = d - return fg_match, bg_match - - def _color_indexes(self, color): - indexes = self.best_match.get(color, None) - if indexes is None: - try: - rgb = int(str(color), 16) - except ValueError: - rgb = 0 - - r = (rgb >> 16) & 0xff - g = (rgb >> 8) & 0xff - b = rgb & 0xff - indexes = self._closest_color(r, g, b) - self.best_match[color] = indexes - return indexes - + (0xff, 0xff, 0xff, FG.GRAY | FG.INTENSITY, BG.GRAY | BG.INTENSITY), + ] + + def _closest_color(self, r, g, b): + distance = 257 * 257 * 3 # "infinity" (>distance from #000000 to #ffffff) + fg_match = 0 + bg_match = 0 + + for r_, g_, b_, fg_, bg_ in self._win32_colors: + rd = r - r_ + gd = g - g_ + bd = b - b_ + + d = rd * rd + gd * gd + bd * bd + + if d < distance: + fg_match = fg_ + bg_match = bg_ + distance = d + return fg_match, bg_match + + def _color_indexes(self, color): + indexes = self.best_match.get(color, None) + if indexes is None: + try: + rgb = int(str(color), 16) + except ValueError: + rgb = 0 + + r = (rgb >> 16) & 0xff + g = (rgb >> 8) & 0xff + b = rgb & 0xff + indexes = self._closest_color(r, g, b) + self.best_match[color] = indexes + return indexes + def lookup_fg_color(self, fg_color): - """ - Return the color for use in the - `windll.kernel32.SetConsoleTextAttribute` API call. - - :param fg_color: Foreground as text. E.g. 'ffffff' or 'red' - """ - # Foreground. - if fg_color in FG_ANSI_COLORS: + """ + Return the color for use in the + `windll.kernel32.SetConsoleTextAttribute` API call. + + :param fg_color: Foreground as text. E.g. 'ffffff' or 'red' + """ + # Foreground. + if fg_color in FG_ANSI_COLORS: return FG_ANSI_COLORS[fg_color] - else: + else: return self._color_indexes(fg_color)[0] - + def lookup_bg_color(self, bg_color): """ Return the color for use in the @@ -549,8 +549,8 @@ class ColorLookupTable(object): :param bg_color: Background as text. E.g. 'ffffff' or 'red' """ - # Background. - if bg_color in BG_ANSI_COLORS: + # Background. + if bg_color in BG_ANSI_COLORS: return BG_ANSI_COLORS[bg_color] - else: + else: return self._color_indexes(bg_color)[1] diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/utils.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/utils.py index 3cd931883c..aab7682184 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/utils.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/utils.py @@ -1,98 +1,98 @@ -from __future__ import unicode_literals +from __future__ import unicode_literals import inspect -import os -import signal -import sys -import threading +import os +import signal +import sys +import threading import weakref - -from wcwidth import wcwidth + +from wcwidth import wcwidth from six.moves import range - - -__all__ = ( + + +__all__ = ( 'Event', - 'DummyContext', - 'get_cwidth', - 'suspend_to_background_supported', - 'is_conemu_ansi', - 'is_windows', - 'in_main_thread', - 'take_using_weights', + 'DummyContext', + 'get_cwidth', + 'suspend_to_background_supported', + 'is_conemu_ansi', + 'is_windows', + 'in_main_thread', + 'take_using_weights', 'test_callable_args', -) - - +) + + class Event(object): - """ + """ Simple event to which event handlers can be attached. For instance:: - + class Cls: def __init__(self): # Define event. The first parameter is the sender. self.event = Event(self) - + obj = Cls() - + def handler(sender): pass - + # Add event handler by using the += operator. obj.event += handler # Fire event. obj.event() - """ + """ def __init__(self, sender, handler=None): self.sender = sender self._handlers = [] - + if handler is not None: self += handler def __call__(self): " Fire event. " - for handler in self._handlers: + for handler in self._handlers: handler(self.sender) - + def fire(self): " Alias for just calling the event. " self() - def __iadd__(self, handler): - """ - Add another handler to this callback. + def __iadd__(self, handler): + """ + Add another handler to this callback. (Handler should be a callable that takes exactly one parameter: the sender object.) - """ + """ # Test handler. assert callable(handler) if not test_callable_args(handler, [None]): raise TypeError("%r doesn't take exactly one argument." % handler) # Add to list of event handlers. - self._handlers.append(handler) - return self - - def __isub__(self, handler): - """ - Remove a handler from this callback. - """ - self._handlers.remove(handler) - return self - - + self._handlers.append(handler) + return self + + def __isub__(self, handler): + """ + Remove a handler from this callback. + """ + self._handlers.remove(handler) + return self + + # Cache of signatures. Improves the performance of `test_callable_args`. _signatures_cache = weakref.WeakKeyDictionary() - - + + def test_callable_args(func, args): """ Return True when this function can be called with the given arguments. """ assert isinstance(args, (list, tuple)) signature = getattr(inspect, 'signature', None) - + if signature is not None: # For Python 3, use inspect.signature. try: @@ -129,112 +129,112 @@ def test_callable_args(func, args): return len(spec.args) - len(spec.defaults or []) <= len(args) <= len(spec.args) -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) - +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) + # Cache for short strings. # (It's hard to tell what we can consider short...) if len(string) < 256: 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' - - -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 + 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' + + +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 diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/validation.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/validation.py index 0027873f88..f898796e06 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/validation.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/validation.py @@ -1,64 +1,64 @@ -""" -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 +""" +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) + + def validate(self, document): + # Call the validator only if the filter is active. + if self.filter(): + self.validator.validate(document) diff --git a/contrib/python/prompt-toolkit/py2/prompt_toolkit/win32_types.py b/contrib/python/prompt-toolkit/py2/prompt_toolkit/win32_types.py index ba2d90df8e..4b7de092f6 100644 --- a/contrib/python/prompt-toolkit/py2/prompt_toolkit/win32_types.py +++ b/contrib/python/prompt-toolkit/py2/prompt_toolkit/win32_types.py @@ -1,155 +1,155 @@ -from 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), - ] +from 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), + ] diff --git a/contrib/python/prompt-toolkit/py2/ya.make b/contrib/python/prompt-toolkit/py2/ya.make index db22aa0a70..ce02faf4d1 100644 --- a/contrib/python/prompt-toolkit/py2/ya.make +++ b/contrib/python/prompt-toolkit/py2/ya.make @@ -1,9 +1,9 @@ PY2_LIBRARY() - + OWNER(blinkov nslus g:python-contrib) VERSION(1.0.18) - + LICENSE(BSD-3-Clause) PEERDIR( @@ -33,120 +33,120 @@ NO_CHECK_IMPORTS( prompt_toolkit.win32_types ) -PY_SRCS( - TOP_LEVEL - prompt_toolkit/__init__.py - prompt_toolkit/application.py - prompt_toolkit/auto_suggest.py - prompt_toolkit/buffer.py - prompt_toolkit/buffer_mapping.py +PY_SRCS( + TOP_LEVEL + prompt_toolkit/__init__.py + prompt_toolkit/application.py + prompt_toolkit/auto_suggest.py + prompt_toolkit/buffer.py + prompt_toolkit/buffer_mapping.py prompt_toolkit/cache.py - prompt_toolkit/clipboard/__init__.py - prompt_toolkit/clipboard/base.py - prompt_toolkit/clipboard/in_memory.py - prompt_toolkit/clipboard/pyperclip.py - prompt_toolkit/completion.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/document.py - prompt_toolkit/enums.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/clipboard/__init__.py + prompt_toolkit/clipboard/base.py + prompt_toolkit/clipboard/in_memory.py + prompt_toolkit/clipboard/pyperclip.py + prompt_toolkit/completion.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/document.py + prompt_toolkit/enums.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/select.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/history.py - prompt_toolkit/input.py - prompt_toolkit/interface.py - prompt_toolkit/key_binding/__init__.py - prompt_toolkit/key_binding/bindings/__init__.py - prompt_toolkit/key_binding/bindings/basic.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/history.py + prompt_toolkit/input.py + prompt_toolkit/interface.py + prompt_toolkit/key_binding/__init__.py + prompt_toolkit/key_binding/bindings/__init__.py + prompt_toolkit/key_binding/bindings/basic.py prompt_toolkit/key_binding/bindings/completion.py - prompt_toolkit/key_binding/bindings/emacs.py + prompt_toolkit/key_binding/bindings/emacs.py prompt_toolkit/key_binding/bindings/named_commands.py - prompt_toolkit/key_binding/bindings/scroll.py - prompt_toolkit/key_binding/bindings/utils.py - prompt_toolkit/key_binding/bindings/vi.py + prompt_toolkit/key_binding/bindings/scroll.py + prompt_toolkit/key_binding/bindings/utils.py + prompt_toolkit/key_binding/bindings/vi.py prompt_toolkit/key_binding/defaults.py prompt_toolkit/key_binding/digraphs.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/keys.py - prompt_toolkit/layout/__init__.py - prompt_toolkit/layout/containers.py - prompt_toolkit/layout/controls.py - prompt_toolkit/layout/dimension.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/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/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/keys.py + prompt_toolkit/layout/__init__.py + prompt_toolkit/layout/containers.py + prompt_toolkit/layout/controls.py + prompt_toolkit/layout/dimension.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/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/__init__.py prompt_toolkit/styles/base.py prompt_toolkit/styles/defaults.py prompt_toolkit/styles/from_dict.py prompt_toolkit/styles/from_pygments.py prompt_toolkit/styles/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 + 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 prompt_toolkit/token.py - prompt_toolkit/utils.py - prompt_toolkit/validation.py - prompt_toolkit/win32_types.py -) - + prompt_toolkit/utils.py + prompt_toolkit/validation.py + prompt_toolkit/win32_types.py +) + RESOURCE_FILES( PREFIX contrib/python/prompt-toolkit/py2/ .dist-info/METADATA .dist-info/top_level.txt ) -END() +END() RECURSE_FOR_TESTS( tests |