Ejemplo n.º 1
0
class _MergedStyle(BaseStyle):
    """
    Merge multiple `Style` objects into one.
    This is supposed to ensure consistency: if any of the given styles changes,
    then this style will be updated.
    """

    # NOTE: previously, we used an algorithm where we did not generate the
    #       combined style. Instead this was a proxy that called one style
    #       after the other, passing the outcome of the previous style as the
    #       default for the next one. This did not work, because that way, the
    #       priorities like described in the `Style` class don't work.
    #       'class:aborted' was for instance never displayed in gray, because
    #       the next style specified a default color for any text. (The
    #       explicit styling of class:aborted should have taken priority,
    #       because it was more precise.)
    def __init__(self, styles):
        assert all(isinstance(style, BaseStyle) for style in styles)

        self.styles = styles
        self._style = SimpleCache(maxsize=1)

    @property
    def _merged_style(self):
        " The `Style` object that has the other styles merged together. "

        def get():
            return Style(self.style_rules)

        return self._style.get(self.invalidation_hash(), get)

    @property
    def style_rules(self):
        style_rules = []
        for s in self.styles:
            style_rules.extend(s.style_rules)
        return style_rules

    def get_attrs_for_style_str(self, style_str, default=DEFAULT_ATTRS):
        return self._merged_style.get_attrs_for_style_str(style_str, default)

    def invalidation_hash(self):
        return tuple(s.invalidation_hash() for s in self.styles)
Ejemplo n.º 2
0
 def __init__(self):
     self.bindings = []
     self._get_bindings_for_keys_cache = SimpleCache(maxsize=10000)
     self._get_bindings_starting_with_keys_cache = SimpleCache(maxsize=1000)
     self.__version = 0  # For cache invalidation.
Ejemplo n.º 3
0
class KeyBindings(KeyBindingsBase):
    """
    A container for a set of key bindings.

    Example usage::

        kb = KeyBindings()

        @kb.add('c-t')
        def _(event):
            print('Control-T pressed')

        @kb.add('c-a', 'c-b')
        def _(event):
            print('Control-A pressed, followed by Control-B')

        @kb.add('c-x', filter=is_searching)
        def _(event):
            print('Control-X pressed')  # Works only if we are searching.

    """
    def __init__(self):
        self.bindings = []
        self._get_bindings_for_keys_cache = SimpleCache(maxsize=10000)
        self._get_bindings_starting_with_keys_cache = SimpleCache(maxsize=1000)
        self.__version = 0  # For cache invalidation.

    def _clear_cache(self):
        self.__version += 1
        self._get_bindings_for_keys_cache.clear()
        self._get_bindings_starting_with_keys_cache.clear()

    @property
    def _version(self):
        return self.__version

    def add(self, *keys, **kwargs):
        """
        Decorator for adding a key bindings.

        :param filter: :class:`~prompt_toolkit.filters.Filter` to determine
            when this key binding is active.
        :param eager: :class:`~prompt_toolkit.filters.Filter` or `bool`.
            When True, ignore potential longer matches when this key binding is
            hit. E.g. when there is an active eager key binding for Ctrl-X,
            execute the handler immediately and ignore the key binding for
            Ctrl-X Ctrl-E of which it is a prefix.
        :param is_global: When this key bindings is added to a `Container` or
            `Control`, make it a global (always active) binding.
        :param save_before: Callable that takes an `Event` and returns True if
            we should save the current buffer, before handling the event.
            (That's the default.)
        :param record_in_macro: Record these key bindings when a macro is
            being recorded. (True by default.)
        """
        filter = to_filter(kwargs.pop('filter', True))
        eager = to_filter(kwargs.pop('eager', False))
        is_global = to_filter(kwargs.pop('is_global', False))
        save_before = kwargs.pop('save_before', lambda e: True)
        record_in_macro = to_filter(kwargs.pop('record_in_macro', True))

        assert not kwargs
        assert keys
        assert callable(save_before)

        keys = tuple(_check_and_expand_key(k) for k in keys)

        if isinstance(filter, Never):
            # When a filter is Never, it will always stay disabled, so in that
            # case don't bother putting it in the key bindings. It will slow
            # down every key press otherwise.
            def decorator(func):
                return func
        else:

            def decorator(func):
                if isinstance(func, _Binding):
                    # We're adding an existing _Binding object.
                    self.bindings.append(
                        _Binding(keys,
                                 func.handler,
                                 filter=func.filter & filter,
                                 eager=eager | func.eager,
                                 is_global=is_global | func.is_global,
                                 save_before=func.save_before,
                                 record_in_macro=func.record_in_macro))
                else:
                    self.bindings.append(
                        _Binding(keys,
                                 func,
                                 filter=filter,
                                 eager=eager,
                                 is_global=is_global,
                                 save_before=save_before,
                                 record_in_macro=record_in_macro))
                self._clear_cache()

                return func

        return decorator

    def remove(self, *args):
        """
        Remove a key binding.

        This expects either a function that was given to `add` method as
        parameter or a sequence of key bindings.

        Raises `ValueError` when no bindings was found.

        Usage::

            remove(handler)  # Pass handler.
            remove('c-x', 'c-a')  # Or pass the key bindings.
        """
        found = False

        if callable(args[0]):
            assert len(args) == 1
            function = args[0]

            # Remove the given function.
            for b in self.bindings:
                if b.handler == function:
                    self.bindings.remove(b)
                    found = True

        else:
            assert len(args) > 0

            # Remove this sequence of key bindings.
            keys = tuple(_check_and_expand_key(k) for k in args)

            for b in self.bindings:
                if b.keys == keys:
                    self.bindings.remove(b)
                    found = True

        if found:
            self._clear_cache()
        else:
            # No key binding found for this function. Raise ValueError.
            raise ValueError('Binding not found: %r' % (function, ))

    # For backwards-compatibility.
    add_binding = add
    remove_binding = remove

    def get_bindings_for_keys(self, keys):
        """
        Return a list of key bindings that can handle this key.
        (This return also inactive bindings, so the `filter` still has to be
        called, for checking it.)

        :param keys: tuple of keys.
        """
        def get():
            result = []
            for b in self.bindings:
                if len(keys) == len(b.keys):
                    match = True
                    any_count = 0

                    for i, j in zip(b.keys, keys):
                        if i != j and i != Keys.Any:
                            match = False
                            break

                        if i == Keys.Any:
                            any_count += 1

                    if match:
                        result.append((any_count, b))

            # Place bindings that have more 'Any' occurrences in them at the end.
            result = sorted(result, key=lambda item: -item[0])

            return [item[1] for item in result]

        return self._get_bindings_for_keys_cache.get(keys, get)

    def get_bindings_starting_with_keys(self, keys):
        """
        Return a list of key bindings that handle a key sequence starting with
        `keys`. (It does only return bindings for which the sequences are
        longer than `keys`. And like `get_bindings_for_keys`, it also includes
        inactive bindings.)

        :param keys: tuple of keys.
        """
        def get():
            result = []
            for b in self.bindings:
                if len(keys) < len(b.keys):
                    match = True
                    for i, j in zip(b.keys, keys):
                        if i != j and i != Keys.Any:
                            match = False
                            break
                    if match:
                        result.append(b)
            return result

        return self._get_bindings_starting_with_keys_cache.get(keys, get)
Ejemplo n.º 4
0
    def __init__(self, styles):
        assert all(isinstance(style, BaseStyle) for style in styles)

        self.styles = styles
        self._style = SimpleCache(maxsize=1)
Ejemplo n.º 5
0
    def __init__(self, chars='[](){}<>', max_cursor_distance=1000):
        self.chars = chars
        self.max_cursor_distance = max_cursor_distance

        self._positions_cache = SimpleCache(maxsize=8)
Ejemplo n.º 6
0
class HighlightMatchingBracketProcessor(Processor):
    """
    When the cursor is on or right after a bracket, it highlights the matching
    bracket.

    :param max_cursor_distance: Only highlight matching brackets when the
        cursor is within this distance. (From inside a `Processor`, we can't
        know which lines will be visible on the screen. But we also don't want
        to scan the whole document for matching brackets on each key press, so
        we limit to this value.)
    """
    _closing_braces = '])}>'

    def __init__(self, chars='[](){}<>', max_cursor_distance=1000):
        self.chars = chars
        self.max_cursor_distance = max_cursor_distance

        self._positions_cache = SimpleCache(maxsize=8)

    def _get_positions_to_highlight(self, document):
        """
        Return a list of (row, col) tuples that need to be highlighted.
        """
        # Try for the character under the cursor.
        if document.current_char and document.current_char in self.chars:
            pos = document.find_matching_bracket_position(
                    start_pos=document.cursor_position - self.max_cursor_distance,
                    end_pos=document.cursor_position + self.max_cursor_distance)

        # Try for the character before the cursor.
        elif (document.char_before_cursor and document.char_before_cursor in
              self._closing_braces and document.char_before_cursor in self.chars):
            document = Document(document.text, document.cursor_position - 1)

            pos = document.find_matching_bracket_position(
                    start_pos=document.cursor_position - self.max_cursor_distance,
                    end_pos=document.cursor_position + self.max_cursor_distance)
        else:
            pos = None

        # Return a list of (row, col) tuples that need to be highlighted.
        if pos:
            pos += document.cursor_position  # pos is relative.
            row, col = document.translate_index_to_position(pos)
            return [(row, col), (document.cursor_position_row, document.cursor_position_col)]
        else:
            return []

    def apply_transformation(self, transformation_input):
        buffer_control, document, lineno, source_to_display, fragments, _, _ = transformation_input.unpack()

        # When the application is in the 'done' state, don't highlight.
        if get_app().is_done:
            return Transformation(fragments)

        # Get the highlight positions.
        key = (get_app().render_counter, document.text, document.cursor_position)
        positions = self._positions_cache.get(
            key, lambda: self._get_positions_to_highlight(document))

        # Apply if positions were found at this line.
        if positions:
            for row, col in positions:
                if row == lineno:
                    col = source_to_display(col)
                    fragments = explode_text_fragments(fragments)
                    style, text = fragments[col]

                    if col == document.cursor_position_col:
                        style += ' class:matching-bracket.cursor '
                    else:
                        style += ' class:matching-bracket.other '

                    fragments[col] = (style, text)

        return Transformation(fragments)
Ejemplo n.º 7
0
 def __init__(self, app):
     self.app = app
     self._cache = SimpleCache()
Ejemplo n.º 8
0
class _CombinedRegistry(KeyBindingsBase):
    """
    The `KeyBindings` of key bindings for a `Application`.
    This merges the global key bindings with the one of the current user
    control.
    """
    def __init__(self, app):
        self.app = app
        self._cache = SimpleCache()

    @property
    def _version(self):
        """ Not needed - this object is not going to be wrapped in another
        KeyBindings object. """
        raise NotImplementedError

    def _create_key_bindings(self, current_window, other_controls):
        """
        Create a `KeyBindings` object that merges the `KeyBindings` from the
        `UIControl` with all the parent controls and the global key bindings.
        """
        key_bindings = []
        collected_containers = set()

        # Collect key bindings from currently focused control and all parent
        # controls. Don't include key bindings of container parent controls.
        container = current_window
        while True:
            collected_containers.add(container)
            kb = container.get_key_bindings()
            if kb is not None:
                key_bindings.append(kb)

            if container.is_modal():
                break

            parent = self.app.layout.get_parent(container)
            if parent is None:
                break
            else:
                container = parent

        # Include global bindings (starting at the top-model container).
        for c in walk(container):
            if c not in collected_containers:
                kb = c.get_key_bindings()
                if kb is not None:
                    key_bindings.append(GlobalOnlyKeyBindings(kb))

        # Add App key bindings
        if self.app.key_bindings:
            key_bindings.append(self.app.key_bindings)

        # Add mouse bindings.
        key_bindings.append(
            ConditionalKeyBindings(self.app._page_navigation_bindings,
                                   self.app.enable_page_navigation_bindings))
        key_bindings.append(self.app._default_bindings)

        # Reverse this list. The current control's key bindings should come
        # last. They need priority.
        key_bindings = key_bindings[::-1]

        return merge_key_bindings(key_bindings)

    @property
    def _key_bindings(self):
        current_window = self.app.layout.current_window
        other_controls = list(self.app.layout.find_all_controls())
        key = current_window, frozenset(other_controls)

        return self._cache.get(
            key,
            lambda: self._create_key_bindings(current_window, other_controls))

    def get_bindings_for_keys(self, keys):
        return self._key_bindings.get_bindings_for_keys(keys)

    def get_bindings_starting_with_keys(self, keys):
        return self._key_bindings.get_bindings_starting_with_keys(keys)