aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout
diff options
context:
space:
mode:
authormonster <monster@ydb.tech>2022-07-07 14:41:37 +0300
committermonster <monster@ydb.tech>2022-07-07 14:41:37 +0300
commit06e5c21a835c0e923506c4ff27929f34e00761c2 (patch)
tree75efcbc6854ef9bd476eb8bf00cc5c900da436a2 /contrib/python/prompt-toolkit/py3/prompt_toolkit/layout
parent03f024c4412e3aa613bb543cf1660176320ba8f4 (diff)
downloadydb-06e5c21a835c0e923506c4ff27929f34e00761c2.tar.gz
fix ya.make
Diffstat (limited to 'contrib/python/prompt-toolkit/py3/prompt_toolkit/layout')
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/__init__.py144
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/containers.py2757
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py957
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dimension.py217
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dummy.py37
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/layout.py411
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/margins.py305
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py723
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/mouse_handlers.py54
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/processors.py1029
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/screen.py328
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/scrollable_pane.py493
-rw-r--r--contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/utils.py80
13 files changed, 0 insertions, 7535 deletions
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/__init__.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/__init__.py
deleted file mode 100644
index 6669da5d7a8..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/__init__.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-Command line layout definitions
--------------------------------
-
-The layout of a command line interface is defined by a Container instance.
-There are two main groups of classes here. Containers and controls:
-
-- A container can contain other containers or controls, it can have multiple
- children and it decides about the dimensions.
-- A control is responsible for rendering the actual content to a screen.
- A control can propose some dimensions, but it's the container who decides
- about the dimensions -- or when the control consumes more space -- which part
- of the control will be visible.
-
-
-Container classes::
-
- - Container (Abstract base class)
- |- HSplit (Horizontal split)
- |- VSplit (Vertical split)
- |- FloatContainer (Container which can also contain menus and other floats)
- `- Window (Container which contains one actual control
-
-Control classes::
-
- - UIControl (Abstract base class)
- |- FormattedTextControl (Renders formatted text, or a simple list of text fragments)
- `- BufferControl (Renders an input buffer.)
-
-
-Usually, you end up wrapping every control inside a `Window` object, because
-that's the only way to render it in a layout.
-
-There are some prepared toolbars which are ready to use::
-
-- SystemToolbar (Shows the 'system' input buffer, for entering system commands.)
-- ArgToolbar (Shows the input 'arg', for repetition of input commands.)
-- SearchToolbar (Shows the 'search' input buffer, for incremental search.)
-- CompletionsToolbar (Shows the completions of the current buffer.)
-- ValidationToolbar (Shows validation errors of the current buffer.)
-
-And one prepared menu:
-
-- CompletionsMenu
-
-"""
-from .containers import (
- AnyContainer,
- ColorColumn,
- ConditionalContainer,
- Container,
- DynamicContainer,
- Float,
- FloatContainer,
- HorizontalAlign,
- HSplit,
- ScrollOffsets,
- VerticalAlign,
- VSplit,
- Window,
- WindowAlign,
- WindowRenderInfo,
- is_container,
- to_container,
- to_window,
-)
-from .controls import (
- BufferControl,
- DummyControl,
- FormattedTextControl,
- SearchBufferControl,
- UIContent,
- UIControl,
-)
-from .dimension import (
- AnyDimension,
- D,
- Dimension,
- is_dimension,
- max_layout_dimensions,
- sum_layout_dimensions,
- to_dimension,
-)
-from .layout import InvalidLayoutError, Layout, walk
-from .margins import (
- ConditionalMargin,
- Margin,
- NumberedMargin,
- PromptMargin,
- ScrollbarMargin,
-)
-from .menus import CompletionsMenu, MultiColumnCompletionsMenu
-from .scrollable_pane import ScrollablePane
-
-__all__ = [
- # Layout.
- "Layout",
- "InvalidLayoutError",
- "walk",
- # Dimensions.
- "AnyDimension",
- "Dimension",
- "D",
- "sum_layout_dimensions",
- "max_layout_dimensions",
- "to_dimension",
- "is_dimension",
- # Containers.
- "AnyContainer",
- "Container",
- "HorizontalAlign",
- "VerticalAlign",
- "HSplit",
- "VSplit",
- "FloatContainer",
- "Float",
- "WindowAlign",
- "Window",
- "WindowRenderInfo",
- "ConditionalContainer",
- "ScrollOffsets",
- "ColorColumn",
- "to_container",
- "to_window",
- "is_container",
- "DynamicContainer",
- "ScrollablePane",
- # Controls.
- "BufferControl",
- "SearchBufferControl",
- "DummyControl",
- "FormattedTextControl",
- "UIControl",
- "UIContent",
- # Margins.
- "Margin",
- "NumberedMargin",
- "ScrollbarMargin",
- "ConditionalMargin",
- "PromptMargin",
- # Menus.
- "CompletionsMenu",
- "MultiColumnCompletionsMenu",
-]
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/containers.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/containers.py
deleted file mode 100644
index 03f9e7d2485..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/containers.py
+++ /dev/null
@@ -1,2757 +0,0 @@
-"""
-Container for the layout.
-(Containers can contain other containers or user interface controls.)
-"""
-from abc import ABCMeta, abstractmethod
-from enum import Enum
-from functools import partial
-from typing import (
- TYPE_CHECKING,
- Callable,
- Dict,
- List,
- Optional,
- Sequence,
- Tuple,
- Union,
- cast,
-)
-
-from prompt_toolkit.application.current import get_app
-from prompt_toolkit.cache import SimpleCache
-from prompt_toolkit.data_structures import Point
-from prompt_toolkit.filters import (
- FilterOrBool,
- emacs_insert_mode,
- to_filter,
- vi_insert_mode,
-)
-from prompt_toolkit.formatted_text import (
- AnyFormattedText,
- StyleAndTextTuples,
- to_formatted_text,
-)
-from prompt_toolkit.formatted_text.utils import (
- fragment_list_to_text,
- fragment_list_width,
-)
-from prompt_toolkit.key_binding import KeyBindingsBase
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
-from prompt_toolkit.utils import get_cwidth, take_using_weights, to_int, to_str
-
-from .controls import (
- DummyControl,
- FormattedTextControl,
- GetLinePrefixCallable,
- UIContent,
- UIControl,
-)
-from .dimension import (
- AnyDimension,
- Dimension,
- max_layout_dimensions,
- sum_layout_dimensions,
- to_dimension,
-)
-from .margins import Margin
-from .mouse_handlers import MouseHandlers
-from .screen import _CHAR_CACHE, Screen, WritePosition
-from .utils import explode_text_fragments
-
-if TYPE_CHECKING:
- from typing_extensions import Protocol, TypeGuard
-
- from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
-
-
-__all__ = [
- "AnyContainer",
- "Container",
- "HorizontalAlign",
- "VerticalAlign",
- "HSplit",
- "VSplit",
- "FloatContainer",
- "Float",
- "WindowAlign",
- "Window",
- "WindowRenderInfo",
- "ConditionalContainer",
- "ScrollOffsets",
- "ColorColumn",
- "to_container",
- "to_window",
- "is_container",
- "DynamicContainer",
-]
-
-
-class Container(metaclass=ABCMeta):
- """
- Base class for user interface layout.
- """
-
- @abstractmethod
- def reset(self) -> None:
- """
- Reset the state of this container and all the children.
- (E.g. reset scroll offsets, etc...)
- """
-
- @abstractmethod
- def preferred_width(self, max_available_width: int) -> Dimension:
- """
- Return a :class:`~prompt_toolkit.layout.Dimension` that represents the
- desired width for this container.
- """
-
- @abstractmethod
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- """
- Return a :class:`~prompt_toolkit.layout.Dimension` that represents the
- desired height for this container.
- """
-
- @abstractmethod
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- """
- Write the actual content to the screen.
-
- :param screen: :class:`~prompt_toolkit.layout.screen.Screen`
- :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`.
- :param parent_style: Style string to pass to the :class:`.Window`
- object. This will be applied to all content of the windows.
- :class:`.VSplit` and :class:`.HSplit` can use it to pass their
- style down to the windows that they contain.
- :param z_index: Used for propagating z_index from parent to child.
- """
-
- def is_modal(self) -> bool:
- """
- When this container is modal, key bindings from parent containers are
- not taken into account if a user control in this container is focused.
- """
- return False
-
- def get_key_bindings(self) -> Optional[KeyBindingsBase]:
- """
- Returns a :class:`.KeyBindings` object. These bindings become active when any
- user control in this container has the focus, except if any containers
- between this container and the focused user control is modal.
- """
- return None
-
- @abstractmethod
- def get_children(self) -> List["Container"]:
- """
- Return the list of child :class:`.Container` objects.
- """
- return []
-
-
-if TYPE_CHECKING:
-
- class MagicContainer(Protocol):
- """
- Any object that implements ``__pt_container__`` represents a container.
- """
-
- def __pt_container__(self) -> "AnyContainer":
- ...
-
-
-AnyContainer = Union[Container, "MagicContainer"]
-
-
-def _window_too_small() -> "Window":
- "Create a `Window` that displays the 'Window too small' text."
- return Window(
- FormattedTextControl(text=[("class:window-too-small", " Window too small... ")])
- )
-
-
-class VerticalAlign(Enum):
- "Alignment for `HSplit`."
- TOP = "TOP"
- CENTER = "CENTER"
- BOTTOM = "BOTTOM"
- JUSTIFY = "JUSTIFY"
-
-
-class HorizontalAlign(Enum):
- "Alignment for `VSplit`."
- LEFT = "LEFT"
- CENTER = "CENTER"
- RIGHT = "RIGHT"
- JUSTIFY = "JUSTIFY"
-
-
-class _Split(Container):
- """
- The common parts of `VSplit` and `HSplit`.
- """
-
- def __init__(
- self,
- children: Sequence[AnyContainer],
- window_too_small: Optional[Container] = None,
- padding: AnyDimension = Dimension.exact(0),
- padding_char: Optional[str] = None,
- padding_style: str = "",
- width: AnyDimension = None,
- height: AnyDimension = None,
- z_index: Optional[int] = None,
- modal: bool = False,
- key_bindings: Optional[KeyBindingsBase] = None,
- style: Union[str, Callable[[], str]] = "",
- ) -> None:
-
- self.children = [to_container(c) for c in children]
- self.window_too_small = window_too_small or _window_too_small()
- self.padding = padding
- self.padding_char = padding_char
- self.padding_style = padding_style
-
- self.width = width
- self.height = height
- self.z_index = z_index
-
- self.modal = modal
- self.key_bindings = key_bindings
- self.style = style
-
- def is_modal(self) -> bool:
- return self.modal
-
- def get_key_bindings(self) -> Optional[KeyBindingsBase]:
- return self.key_bindings
-
- def get_children(self) -> List[Container]:
- return self.children
-
-
-class HSplit(_Split):
- """
- Several layouts, one stacked above/under the other. ::
-
- +--------------------+
- | |
- +--------------------+
- | |
- +--------------------+
-
- By default, this doesn't display a horizontal line between the children,
- but if this is something you need, then create a HSplit as follows::
-
- HSplit(children=[ ... ], padding_char='-',
- padding=1, padding_style='#ffff00')
-
- :param children: List of child :class:`.Container` objects.
- :param window_too_small: A :class:`.Container` object that is displayed if
- there is not enough space for all the children. By default, this is a
- "Window too small" message.
- :param align: `VerticalAlign` value.
- :param width: When given, use this width instead of looking at the children.
- :param height: When given, use this height instead of looking at the children.
- :param z_index: (int or None) When specified, this can be used to bring
- element in front of floating elements. `None` means: inherit from parent.
- :param style: A style string.
- :param modal: ``True`` or ``False``.
- :param key_bindings: ``None`` or a :class:`.KeyBindings` object.
-
- :param padding: (`Dimension` or int), size to be used for the padding.
- :param padding_char: Character to be used for filling in the padding.
- :param padding_style: Style to applied to the padding.
- """
-
- def __init__(
- self,
- children: Sequence[AnyContainer],
- window_too_small: Optional[Container] = None,
- align: VerticalAlign = VerticalAlign.JUSTIFY,
- padding: AnyDimension = 0,
- padding_char: Optional[str] = None,
- padding_style: str = "",
- width: AnyDimension = None,
- height: AnyDimension = None,
- z_index: Optional[int] = None,
- modal: bool = False,
- key_bindings: Optional[KeyBindingsBase] = None,
- style: Union[str, Callable[[], str]] = "",
- ) -> None:
-
- super().__init__(
- children=children,
- window_too_small=window_too_small,
- padding=padding,
- padding_char=padding_char,
- padding_style=padding_style,
- width=width,
- height=height,
- z_index=z_index,
- modal=modal,
- key_bindings=key_bindings,
- style=style,
- )
-
- self.align = align
-
- self._children_cache: SimpleCache[
- Tuple[Container, ...], List[Container]
- ] = SimpleCache(maxsize=1)
- self._remaining_space_window = Window() # Dummy window.
-
- def preferred_width(self, max_available_width: int) -> Dimension:
- if self.width is not None:
- return to_dimension(self.width)
-
- if self.children:
- dimensions = [c.preferred_width(max_available_width) for c in self.children]
- return max_layout_dimensions(dimensions)
- else:
- return Dimension()
-
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- if self.height is not None:
- return to_dimension(self.height)
-
- dimensions = [
- c.preferred_height(width, max_available_height) for c in self._all_children
- ]
- return sum_layout_dimensions(dimensions)
-
- def reset(self) -> None:
- for c in self.children:
- c.reset()
-
- @property
- def _all_children(self) -> List[Container]:
- """
- List of child objects, including padding.
- """
-
- def get() -> List[Container]:
- result: List[Container] = []
-
- # Padding Top.
- if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM):
- result.append(Window(width=Dimension(preferred=0)))
-
- # The children with padding.
- for child in self.children:
- result.append(child)
- result.append(
- Window(
- height=self.padding,
- char=self.padding_char,
- style=self.padding_style,
- )
- )
- if result:
- result.pop()
-
- # Padding right.
- if self.align in (VerticalAlign.CENTER, VerticalAlign.TOP):
- result.append(Window(width=Dimension(preferred=0)))
-
- return result
-
- return self._children_cache.get(tuple(self.children), get)
-
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- """
- Render the prompt to a `Screen` instance.
-
- :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class
- to which the output has to be written.
- """
- sizes = self._divide_heights(write_position)
- style = parent_style + " " + to_str(self.style)
- z_index = z_index if self.z_index is None else self.z_index
-
- if sizes is None:
- self.window_too_small.write_to_screen(
- screen, mouse_handlers, write_position, style, erase_bg, z_index
- )
- else:
- #
- ypos = write_position.ypos
- xpos = write_position.xpos
- width = write_position.width
-
- # Draw child panes.
- for s, c in zip(sizes, self._all_children):
- c.write_to_screen(
- screen,
- mouse_handlers,
- WritePosition(xpos, ypos, width, s),
- style,
- erase_bg,
- z_index,
- )
- ypos += s
-
- # Fill in the remaining space. This happens when a child control
- # refuses to take more space and we don't have any padding. Adding a
- # dummy child control for this (in `self._all_children`) is not
- # desired, because in some situations, it would take more space, even
- # when it's not required. This is required to apply the styling.
- remaining_height = write_position.ypos + write_position.height - ypos
- if remaining_height > 0:
- self._remaining_space_window.write_to_screen(
- screen,
- mouse_handlers,
- WritePosition(xpos, ypos, width, remaining_height),
- style,
- erase_bg,
- z_index,
- )
-
- def _divide_heights(self, write_position: WritePosition) -> Optional[List[int]]:
- """
- Return the heights for all rows.
- Or None when there is not enough space.
- """
- if not self.children:
- return []
-
- width = write_position.width
- height = write_position.height
-
- # Calculate heights.
- dimensions = [c.preferred_height(width, height) for c in self._all_children]
-
- # Sum dimensions
- sum_dimensions = sum_layout_dimensions(dimensions)
-
- # If there is not enough space for both.
- # Don't do anything.
- if sum_dimensions.min > height:
- return None
-
- # Find optimal sizes. (Start with minimal size, increase until we cover
- # the whole height.)
- sizes = [d.min for d in dimensions]
-
- child_generator = take_using_weights(
- items=list(range(len(dimensions))), weights=[d.weight for d in dimensions]
- )
-
- i = next(child_generator)
-
- # Increase until we meet at least the 'preferred' size.
- preferred_stop = min(height, sum_dimensions.preferred)
- preferred_dimensions = [d.preferred for d in dimensions]
-
- while sum(sizes) < preferred_stop:
- if sizes[i] < preferred_dimensions[i]:
- sizes[i] += 1
- i = next(child_generator)
-
- # Increase until we use all the available space. (or until "max")
- if not get_app().is_done:
- max_stop = min(height, sum_dimensions.max)
- max_dimensions = [d.max for d in dimensions]
-
- while sum(sizes) < max_stop:
- if sizes[i] < max_dimensions[i]:
- sizes[i] += 1
- i = next(child_generator)
-
- return sizes
-
-
-class VSplit(_Split):
- """
- Several layouts, one stacked left/right of the other. ::
-
- +---------+----------+
- | | |
- | | |
- +---------+----------+
-
- By default, this doesn't display a vertical line between the children, but
- if this is something you need, then create a HSplit as follows::
-
- VSplit(children=[ ... ], padding_char='|',
- padding=1, padding_style='#ffff00')
-
- :param children: List of child :class:`.Container` objects.
- :param window_too_small: A :class:`.Container` object that is displayed if
- there is not enough space for all the children. By default, this is a
- "Window too small" message.
- :param align: `HorizontalAlign` value.
- :param width: When given, use this width instead of looking at the children.
- :param height: When given, use this height instead of looking at the children.
- :param z_index: (int or None) When specified, this can be used to bring
- element in front of floating elements. `None` means: inherit from parent.
- :param style: A style string.
- :param modal: ``True`` or ``False``.
- :param key_bindings: ``None`` or a :class:`.KeyBindings` object.
-
- :param padding: (`Dimension` or int), size to be used for the padding.
- :param padding_char: Character to be used for filling in the padding.
- :param padding_style: Style to applied to the padding.
- """
-
- def __init__(
- self,
- children: Sequence[AnyContainer],
- window_too_small: Optional[Container] = None,
- align: HorizontalAlign = HorizontalAlign.JUSTIFY,
- padding: AnyDimension = 0,
- padding_char: Optional[str] = None,
- padding_style: str = "",
- width: AnyDimension = None,
- height: AnyDimension = None,
- z_index: Optional[int] = None,
- modal: bool = False,
- key_bindings: Optional[KeyBindingsBase] = None,
- style: Union[str, Callable[[], str]] = "",
- ) -> None:
-
- super().__init__(
- children=children,
- window_too_small=window_too_small,
- padding=padding,
- padding_char=padding_char,
- padding_style=padding_style,
- width=width,
- height=height,
- z_index=z_index,
- modal=modal,
- key_bindings=key_bindings,
- style=style,
- )
-
- self.align = align
-
- self._children_cache: SimpleCache[
- Tuple[Container, ...], List[Container]
- ] = SimpleCache(maxsize=1)
- self._remaining_space_window = Window() # Dummy window.
-
- def preferred_width(self, max_available_width: int) -> Dimension:
- if self.width is not None:
- return to_dimension(self.width)
-
- dimensions = [
- c.preferred_width(max_available_width) for c in self._all_children
- ]
-
- return sum_layout_dimensions(dimensions)
-
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- if self.height is not None:
- return to_dimension(self.height)
-
- # At the point where we want to calculate the heights, the widths have
- # already been decided. So we can trust `width` to be the actual
- # `width` that's going to be used for the rendering. So,
- # `divide_widths` is supposed to use all of the available width.
- # Using only the `preferred` width caused a bug where the reported
- # height was more than required. (we had a `BufferControl` which did
- # wrap lines because of the smaller width returned by `_divide_widths`.
-
- sizes = self._divide_widths(width)
- children = self._all_children
-
- if sizes is None:
- return Dimension()
- else:
- dimensions = [
- c.preferred_height(s, max_available_height)
- for s, c in zip(sizes, children)
- ]
- return max_layout_dimensions(dimensions)
-
- def reset(self) -> None:
- for c in self.children:
- c.reset()
-
- @property
- def _all_children(self) -> List[Container]:
- """
- List of child objects, including padding.
- """
-
- def get() -> List[Container]:
- result: List[Container] = []
-
- # Padding left.
- if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT):
- result.append(Window(width=Dimension(preferred=0)))
-
- # The children with padding.
- for child in self.children:
- result.append(child)
- result.append(
- Window(
- width=self.padding,
- char=self.padding_char,
- style=self.padding_style,
- )
- )
- if result:
- result.pop()
-
- # Padding right.
- if self.align in (HorizontalAlign.CENTER, HorizontalAlign.LEFT):
- result.append(Window(width=Dimension(preferred=0)))
-
- return result
-
- return self._children_cache.get(tuple(self.children), get)
-
- def _divide_widths(self, width: int) -> Optional[List[int]]:
- """
- Return the widths for all columns.
- Or None when there is not enough space.
- """
- children = self._all_children
-
- if not children:
- return []
-
- # Calculate widths.
- dimensions = [c.preferred_width(width) for c in children]
- preferred_dimensions = [d.preferred for d in dimensions]
-
- # Sum dimensions
- sum_dimensions = sum_layout_dimensions(dimensions)
-
- # If there is not enough space for both.
- # Don't do anything.
- if sum_dimensions.min > width:
- return None
-
- # Find optimal sizes. (Start with minimal size, increase until we cover
- # the whole width.)
- sizes = [d.min for d in dimensions]
-
- child_generator = take_using_weights(
- items=list(range(len(dimensions))), weights=[d.weight for d in dimensions]
- )
-
- i = next(child_generator)
-
- # Increase until we meet at least the 'preferred' size.
- preferred_stop = min(width, sum_dimensions.preferred)
-
- while sum(sizes) < preferred_stop:
- if sizes[i] < preferred_dimensions[i]:
- sizes[i] += 1
- i = next(child_generator)
-
- # Increase until we use all the available space.
- max_dimensions = [d.max for d in dimensions]
- max_stop = min(width, sum_dimensions.max)
-
- while sum(sizes) < max_stop:
- if sizes[i] < max_dimensions[i]:
- sizes[i] += 1
- i = next(child_generator)
-
- return sizes
-
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- """
- Render the prompt to a `Screen` instance.
-
- :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class
- to which the output has to be written.
- """
- if not self.children:
- return
-
- children = self._all_children
- sizes = self._divide_widths(write_position.width)
- style = parent_style + " " + to_str(self.style)
- z_index = z_index if self.z_index is None else self.z_index
-
- # If there is not enough space.
- if sizes is None:
- self.window_too_small.write_to_screen(
- screen, mouse_handlers, write_position, style, erase_bg, z_index
- )
- return
-
- # Calculate heights, take the largest possible, but not larger than
- # write_position.height.
- heights = [
- child.preferred_height(width, write_position.height).preferred
- for width, child in zip(sizes, children)
- ]
- height = max(write_position.height, min(write_position.height, max(heights)))
-
- #
- ypos = write_position.ypos
- xpos = write_position.xpos
-
- # Draw all child panes.
- for s, c in zip(sizes, children):
- c.write_to_screen(
- screen,
- mouse_handlers,
- WritePosition(xpos, ypos, s, height),
- style,
- erase_bg,
- z_index,
- )
- xpos += s
-
- # Fill in the remaining space. This happens when a child control
- # refuses to take more space and we don't have any padding. Adding a
- # dummy child control for this (in `self._all_children`) is not
- # desired, because in some situations, it would take more space, even
- # when it's not required. This is required to apply the styling.
- remaining_width = write_position.xpos + write_position.width - xpos
- if remaining_width > 0:
- self._remaining_space_window.write_to_screen(
- screen,
- mouse_handlers,
- WritePosition(xpos, ypos, remaining_width, height),
- style,
- erase_bg,
- z_index,
- )
-
-
-class FloatContainer(Container):
- """
- Container which can contain another container for the background, as well
- as a list of floating containers on top of it.
-
- Example Usage::
-
- FloatContainer(content=Window(...),
- floats=[
- Float(xcursor=True,
- ycursor=True,
- content=CompletionsMenu(...))
- ])
-
- :param z_index: (int or None) When specified, this can be used to bring
- element in front of floating elements. `None` means: inherit from parent.
- This is the z_index for the whole `Float` container as a whole.
- """
-
- def __init__(
- self,
- content: AnyContainer,
- floats: List["Float"],
- modal: bool = False,
- key_bindings: Optional[KeyBindingsBase] = None,
- style: Union[str, Callable[[], str]] = "",
- z_index: Optional[int] = None,
- ) -> None:
-
- self.content = to_container(content)
- self.floats = floats
-
- self.modal = modal
- self.key_bindings = key_bindings
- self.style = style
- self.z_index = z_index
-
- def reset(self) -> None:
- self.content.reset()
-
- for f in self.floats:
- f.content.reset()
-
- def preferred_width(self, max_available_width: int) -> Dimension:
- return self.content.preferred_width(max_available_width)
-
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- """
- Return the preferred height of the float container.
- (We don't care about the height of the floats, they should always fit
- into the dimensions provided by the container.)
- """
- return self.content.preferred_height(width, max_available_height)
-
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- style = parent_style + " " + to_str(self.style)
- z_index = z_index if self.z_index is None else self.z_index
-
- self.content.write_to_screen(
- screen, mouse_handlers, write_position, style, erase_bg, z_index
- )
-
- for number, fl in enumerate(self.floats):
- # z_index of a Float is computed by summing the z_index of the
- # container and the `Float`.
- new_z_index = (z_index or 0) + fl.z_index
- style = parent_style + " " + to_str(self.style)
-
- # If the float that we have here, is positioned relative to the
- # cursor position, but the Window that specifies the cursor
- # position is not drawn yet, because it's a Float itself, we have
- # to postpone this calculation. (This is a work-around, but good
- # enough for now.)
- postpone = fl.xcursor is not None or fl.ycursor is not None
-
- if postpone:
- new_z_index = (
- number + 10**8
- ) # Draw as late as possible, but keep the order.
- screen.draw_with_z_index(
- z_index=new_z_index,
- draw_func=partial(
- self._draw_float,
- fl,
- screen,
- mouse_handlers,
- write_position,
- style,
- erase_bg,
- new_z_index,
- ),
- )
- else:
- self._draw_float(
- fl,
- screen,
- mouse_handlers,
- write_position,
- style,
- erase_bg,
- new_z_index,
- )
-
- def _draw_float(
- self,
- fl: "Float",
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- "Draw a single Float."
- # When a menu_position was given, use this instead of the cursor
- # position. (These cursor positions are absolute, translate again
- # relative to the write_position.)
- # Note: This should be inside the for-loop, because one float could
- # set the cursor position to be used for the next one.
- cpos = screen.get_menu_position(
- fl.attach_to_window or get_app().layout.current_window
- )
- cursor_position = Point(
- x=cpos.x - write_position.xpos, y=cpos.y - write_position.ypos
- )
-
- fl_width = fl.get_width()
- fl_height = fl.get_height()
- width: int
- height: int
- xpos: int
- ypos: int
-
- # 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
- # Near x position of cursor.
- elif fl.xcursor:
- if fl_width is None:
- width = fl.content.preferred_width(write_position.width).preferred
- width = min(write_position.width, width)
- else:
- width = fl_width
-
- xpos = cursor_position.x
- if xpos + width > write_position.width:
- xpos = max(0, write_position.width - width)
- # Only width given -> center horizontally.
- elif fl_width:
- xpos = int((write_position.width - fl_width) / 2)
- width = fl_width
- # Otherwise, take preferred width from float content.
- else:
- width = fl.content.preferred_width(write_position.width).preferred
-
- if fl.left is not None:
- xpos = fl.left
- elif fl.right is not None:
- xpos = max(0, write_position.width - width - fl.right)
- else: # Center horizontally.
- xpos = max(0, int((write_position.width - width) / 2))
-
- # Trim.
- width = min(width, write_position.width - xpos)
-
- # Top & height given.
- if fl.top is not None and fl_height is not None:
- ypos = fl.top
- height = fl_height
- # Top & bottom given -> calculate height.
- elif fl.top is not None and fl.bottom is not None:
- ypos = fl.top
- height = write_position.height - fl.top - fl.bottom
- # Height & bottom given -> calculate top.
- elif fl_height is not None and fl.bottom is not None:
- ypos = write_position.height - fl_height - fl.bottom
- height = fl_height
- # Near cursor.
- elif fl.ycursor:
- ypos = cursor_position.y + (0 if fl.allow_cover_cursor else 1)
-
- if fl_height is None:
- height = fl.content.preferred_height(
- width, write_position.height
- ).preferred
- else:
- height = fl_height
-
- # Reduce height if not enough space. (We can use the height
- # when the content requires it.)
- if height > write_position.height - ypos:
- if write_position.height - ypos + 1 >= ypos:
- # When the space below the cursor is more than
- # the space above, just reduce the height.
- height = write_position.height - ypos
- else:
- # Otherwise, fit the float above the cursor.
- height = min(height, cursor_position.y)
- ypos = cursor_position.y - height
-
- # Only height given -> center vertically.
- elif fl_height:
- ypos = int((write_position.height - fl_height) / 2)
- height = fl_height
- # Otherwise, take preferred height from content.
- else:
- height = fl.content.preferred_height(width, write_position.height).preferred
-
- if fl.top is not None:
- ypos = fl.top
- elif fl.bottom is not None:
- ypos = max(0, write_position.height - height - fl.bottom)
- else: # Center vertically.
- ypos = max(0, int((write_position.height - height) / 2))
-
- # Trim.
- height = min(height, write_position.height - ypos)
-
- # Write float.
- # (xpos and ypos can be negative: a float can be partially visible.)
- if height > 0 and width > 0:
- wp = WritePosition(
- xpos=xpos + write_position.xpos,
- ypos=ypos + write_position.ypos,
- width=width,
- height=height,
- )
-
- if not fl.hide_when_covering_content or self._area_is_empty(screen, wp):
- fl.content.write_to_screen(
- screen,
- mouse_handlers,
- wp,
- style,
- erase_bg=not fl.transparent(),
- z_index=z_index,
- )
-
- def _area_is_empty(self, screen: Screen, write_position: WritePosition) -> bool:
- """
- Return True when the area below the write position is still empty.
- (For floats that should not hide content underneath.)
- """
- wp = write_position
-
- for y in range(wp.ypos, wp.ypos + wp.height):
- if y in screen.data_buffer:
- row = screen.data_buffer[y]
-
- for x in range(wp.xpos, wp.xpos + wp.width):
- c = row[x]
- if c.char != " ":
- return False
-
- return True
-
- def is_modal(self) -> bool:
- return self.modal
-
- def get_key_bindings(self) -> Optional[KeyBindingsBase]:
- return self.key_bindings
-
- def get_children(self) -> List[Container]:
- children = [self.content]
- children.extend(f.content for f in self.floats)
- return children
-
-
-class Float:
- """
- Float for use in a :class:`.FloatContainer`.
- Except for the `content` parameter, all other options are optional.
-
- :param content: :class:`.Container` instance.
-
- :param width: :class:`.Dimension` or callable which returns a :class:`.Dimension`.
- :param height: :class:`.Dimension` or callable which returns a :class:`.Dimension`.
-
- :param left: Distance to the left edge of the :class:`.FloatContainer`.
- :param right: Distance to the right edge of the :class:`.FloatContainer`.
- :param top: Distance to the top of the :class:`.FloatContainer`.
- :param bottom: Distance to the bottom of the :class:`.FloatContainer`.
-
- :param attach_to_window: Attach to the cursor from this window, instead of
- the current window.
- :param hide_when_covering_content: Hide the float when it covers content underneath.
- :param allow_cover_cursor: When `False`, make sure to display the float
- below the cursor. Not on top of the indicated position.
- :param z_index: Z-index position. For a Float, this needs to be at least
- one. It is relative to the z_index of the parent container.
- :param transparent: :class:`.Filter` indicating whether this float needs to be
- drawn transparently.
- """
-
- def __init__(
- self,
- content: AnyContainer,
- top: Optional[int] = None,
- right: Optional[int] = None,
- bottom: Optional[int] = None,
- left: Optional[int] = None,
- width: Optional[Union[int, Callable[[], int]]] = None,
- height: Optional[Union[int, Callable[[], int]]] = None,
- xcursor: bool = False,
- ycursor: bool = False,
- attach_to_window: Optional[AnyContainer] = None,
- hide_when_covering_content: bool = False,
- allow_cover_cursor: bool = False,
- z_index: int = 1,
- transparent: bool = False,
- ) -> None:
-
- assert z_index >= 1
-
- self.left = left
- self.right = right
- self.top = top
- self.bottom = bottom
-
- self.width = width
- self.height = height
-
- self.xcursor = xcursor
- self.ycursor = ycursor
-
- self.attach_to_window = (
- to_window(attach_to_window) if attach_to_window else None
- )
-
- self.content = to_container(content)
- self.hide_when_covering_content = hide_when_covering_content
- self.allow_cover_cursor = allow_cover_cursor
- self.z_index = z_index
- self.transparent = to_filter(transparent)
-
- def get_width(self) -> Optional[int]:
- if callable(self.width):
- return self.width()
- return self.width
-
- def get_height(self) -> Optional[int]:
- if callable(self.height):
- return self.height()
- return self.height
-
- def __repr__(self) -> str:
- return "Float(content=%r)" % self.content
-
-
-class WindowRenderInfo:
- """
- Render information for the last render time of this control.
- It stores mapping information between the input buffers (in case of a
- :class:`~prompt_toolkit.layout.controls.BufferControl`) and the actual
- render position on the output screen.
-
- (Could be used for implementation of the Vi 'H' and 'L' key bindings as
- well as implementing mouse support.)
-
- :param ui_content: The original :class:`.UIContent` instance that contains
- the whole input, without clipping. (ui_content)
- :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance.
- :param vertical_scroll: The vertical scroll of the :class:`.Window` instance.
- :param window_width: The width of the window that displays the content,
- without the margins.
- :param window_height: The height of the window that displays the content.
- :param configured_scroll_offsets: The scroll offsets as configured for the
- :class:`Window` instance.
- :param visible_line_to_row_col: Mapping that maps the row numbers on the
- displayed screen (starting from zero for the first visible line) to
- (row, col) tuples pointing to the row and column of the :class:`.UIContent`.
- :param rowcol_to_yx: Mapping that maps (row, column) tuples representing
- coordinates of the :class:`UIContent` to (y, x) absolute coordinates at
- the rendered screen.
- """
-
- def __init__(
- self,
- window: "Window",
- ui_content: UIContent,
- horizontal_scroll: int,
- vertical_scroll: int,
- window_width: int,
- window_height: int,
- configured_scroll_offsets: "ScrollOffsets",
- visible_line_to_row_col: Dict[int, Tuple[int, int]],
- rowcol_to_yx: Dict[Tuple[int, int], Tuple[int, int]],
- x_offset: int,
- y_offset: int,
- wrap_lines: bool,
- ) -> None:
-
- self.window = window
- self.ui_content = ui_content
- self.vertical_scroll = vertical_scroll
- self.window_width = window_width # Width without margins.
- self.window_height = window_height
-
- self.configured_scroll_offsets = configured_scroll_offsets
- self.visible_line_to_row_col = visible_line_to_row_col
- self.wrap_lines = wrap_lines
-
- self._rowcol_to_yx = rowcol_to_yx # row/col from input to absolute y/x
- # screen coordinates.
- self._x_offset = x_offset
- self._y_offset = y_offset
-
- @property
- def visible_line_to_input_line(self) -> Dict[int, int]:
- return {
- visible_line: rowcol[0]
- for visible_line, rowcol in self.visible_line_to_row_col.items()
- }
-
- @property
- def cursor_position(self) -> Point:
- """
- Return the cursor position coordinates, relative to the left/top corner
- of the rendered screen.
- """
- cpos = self.ui_content.cursor_position
- try:
- y, x = self._rowcol_to_yx[cpos.y, cpos.x]
- except KeyError:
- # For `DummyControl` for instance, the content can be empty, and so
- # will `_rowcol_to_yx` be. Return 0/0 by default.
- return Point(x=0, y=0)
- else:
- return Point(x=x - self._x_offset, y=y - self._y_offset)
-
- @property
- def applied_scroll_offsets(self) -> "ScrollOffsets":
- """
- Return a :class:`.ScrollOffsets` instance that indicates the actual
- offset. This can be less than or equal to what's configured. E.g, when
- the cursor is completely at the top, the top offset will be zero rather
- than what's configured.
- """
- if self.displayed_lines[0] == 0:
- top = 0
- else:
- # Get row where the cursor is displayed.
- y = self.input_line_to_visible_line[self.ui_content.cursor_position.y]
- top = min(y, self.configured_scroll_offsets.top)
-
- return ScrollOffsets(
- top=top,
- bottom=min(
- self.ui_content.line_count - self.displayed_lines[-1] - 1,
- self.configured_scroll_offsets.bottom,
- ),
- # For left/right, it probably doesn't make sense to return something.
- # (We would have to calculate the widths of all the lines and keep
- # double width characters in mind.)
- left=0,
- right=0,
- )
-
- @property
- def displayed_lines(self) -> List[int]:
- """
- 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) -> Dict[int, int]:
- """
- Return the dictionary mapping the line numbers of the input buffer to
- the lines of the screen. When a line spans several rows at the screen,
- the first row appears in the dictionary.
- """
- result: Dict[int, int] = {}
- for k, v in self.visible_line_to_input_line.items():
- if v in result:
- result[v] = min(result[v], k)
- else:
- result[v] = k
- return result
-
- def first_visible_line(self, after_scroll_offset: bool = False) -> int:
- """
- 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: bool = False) -> int:
- """
- 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: bool = False, after_scroll_offset: bool = False
- ) -> int:
- """
- 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) -> int:
- """
- The full height of the user control.
- """
- return self.ui_content.line_count
-
- @property
- def full_height_visible(self) -> bool:
- """
- 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) -> bool:
- """
- True when the top of the buffer is visible.
- """
- return self.vertical_scroll == 0
-
- @property
- def bottom_visible(self) -> bool:
- """
- True when the bottom of the buffer is visible.
- """
- return self.last_visible_line() == self.content_height - 1
-
- @property
- def vertical_scroll_percentage(self) -> int:
- """
- 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: int) -> int:
- """
- Return the height of the given line.
- (The height that it would take, if this line became visible.)
- """
- if self.wrap_lines:
- return self.ui_content.get_height_for_line(
- lineno, self.window_width, self.window.get_line_prefix
- )
- else:
- return 1
-
-
-class ScrollOffsets:
- """
- Scroll offsets for the :class:`.Window` class.
-
- Note that left/right offsets only make sense if line wrapping is disabled.
- """
-
- def __init__(
- self,
- top: Union[int, Callable[[], int]] = 0,
- bottom: Union[int, Callable[[], int]] = 0,
- left: Union[int, Callable[[], int]] = 0,
- right: Union[int, Callable[[], int]] = 0,
- ) -> None:
-
- self._top = top
- self._bottom = bottom
- self._left = left
- self._right = right
-
- @property
- def top(self) -> int:
- return to_int(self._top)
-
- @property
- def bottom(self) -> int:
- return to_int(self._bottom)
-
- @property
- def left(self) -> int:
- return to_int(self._left)
-
- @property
- def right(self) -> int:
- return to_int(self._right)
-
- def __repr__(self) -> str:
- return "ScrollOffsets(top={!r}, bottom={!r}, left={!r}, right={!r})".format(
- self._top,
- self._bottom,
- self._left,
- self._right,
- )
-
-
-class ColorColumn:
- """
- Column for a :class:`.Window` to be colored.
- """
-
- def __init__(self, position: int, style: str = "class:color-column") -> None:
- self.position = position
- self.style = style
-
-
-_in_insert_mode = vi_insert_mode | emacs_insert_mode
-
-
-class WindowAlign(Enum):
- """
- Alignment of the Window content.
-
- Note that this is different from `HorizontalAlign` and `VerticalAlign`,
- which are used for the alignment of the child containers in respectively
- `VSplit` and `HSplit`.
- """
-
- LEFT = "LEFT"
- RIGHT = "RIGHT"
- CENTER = "CENTER"
-
-
-class Window(Container):
- """
- Container that holds a control.
-
- :param content: :class:`.UIControl` instance.
- :param width: :class:`.Dimension` instance or callable.
- :param height: :class:`.Dimension` instance or callable.
- :param z_index: When specified, this can be used to bring element in front
- of floating elements.
- :param dont_extend_width: When `True`, don't take up more width then the
- preferred width reported by the control.
- :param dont_extend_height: When `True`, don't take up more width then the
- preferred height reported by the control.
- :param ignore_content_width: A `bool` or :class:`.Filter` instance. Ignore
- the :class:`.UIContent` width when calculating the dimensions.
- :param ignore_content_height: A `bool` or :class:`.Filter` instance. Ignore
- the :class:`.UIContent` height when calculating the dimensions.
- :param left_margins: A list of :class:`.Margin` instance to be displayed on
- the left. For instance: :class:`~prompt_toolkit.layout.NumberedMargin`
- can be one of them in order to show line numbers.
- :param right_margins: Like `left_margins`, but on the other side.
- :param scroll_offsets: :class:`.ScrollOffsets` instance, representing the
- preferred amount of lines/columns to be always visible before/after the
- cursor. When both top and bottom are a very high number, the cursor
- will be centered vertically most of the time.
- :param allow_scroll_beyond_bottom: A `bool` or
- :class:`.Filter` instance. When True, allow scrolling so far, that the
- top part of the content is not visible anymore, while there is still
- empty space available at the bottom of the window. In the Vi editor for
- instance, this is possible. You will see tildes while the top part of
- the body is hidden.
- :param wrap_lines: A `bool` or :class:`.Filter` instance. When True, don't
- scroll horizontally, but wrap lines instead.
- :param get_vertical_scroll: Callable that takes this window
- instance as input and returns a preferred vertical scroll.
- (When this is `None`, the scroll is only determined by the last and
- current cursor position.)
- :param get_horizontal_scroll: Callable that takes this window
- instance as input and returns a preferred vertical scroll.
- :param always_hide_cursor: A `bool` or
- :class:`.Filter` instance. When True, never display the cursor, even
- when the user control specifies a cursor position.
- :param cursorline: A `bool` or :class:`.Filter` instance. When True,
- display a cursorline.
- :param cursorcolumn: A `bool` or :class:`.Filter` instance. When True,
- display a cursorcolumn.
- :param colorcolumns: A list of :class:`.ColorColumn` instances that
- describe the columns to be highlighted, or a callable that returns such
- a list.
- :param align: :class:`.WindowAlign` value or callable that returns an
- :class:`.WindowAlign` value. alignment of content.
- :param style: A style string. Style to be applied to all the cells in this
- window. (This can be a callable that returns a string.)
- :param char: (string) Character to be used for filling the background. This
- can also be a callable that returns a character.
- :param get_line_prefix: None or a callable that returns formatted text to
- be inserted before a line. It takes a line number (int) and a
- wrap_count and returns formatted text. This can be used for
- implementation of line continuations, things like Vim "breakindent" and
- so on.
- """
-
- def __init__(
- self,
- content: Optional[UIControl] = None,
- width: AnyDimension = None,
- height: AnyDimension = None,
- z_index: Optional[int] = None,
- dont_extend_width: FilterOrBool = False,
- dont_extend_height: FilterOrBool = False,
- ignore_content_width: FilterOrBool = False,
- ignore_content_height: FilterOrBool = False,
- left_margins: Optional[Sequence[Margin]] = None,
- right_margins: Optional[Sequence[Margin]] = None,
- scroll_offsets: Optional[ScrollOffsets] = None,
- allow_scroll_beyond_bottom: FilterOrBool = False,
- wrap_lines: FilterOrBool = False,
- get_vertical_scroll: Optional[Callable[["Window"], int]] = None,
- get_horizontal_scroll: Optional[Callable[["Window"], int]] = None,
- always_hide_cursor: FilterOrBool = False,
- cursorline: FilterOrBool = False,
- cursorcolumn: FilterOrBool = False,
- colorcolumns: Union[
- None, List[ColorColumn], Callable[[], List[ColorColumn]]
- ] = None,
- align: Union[WindowAlign, Callable[[], WindowAlign]] = WindowAlign.LEFT,
- style: Union[str, Callable[[], str]] = "",
- char: Union[None, str, Callable[[], str]] = None,
- get_line_prefix: Optional[GetLinePrefixCallable] = None,
- ) -> None:
-
- self.allow_scroll_beyond_bottom = to_filter(allow_scroll_beyond_bottom)
- self.always_hide_cursor = to_filter(always_hide_cursor)
- self.wrap_lines = to_filter(wrap_lines)
- self.cursorline = to_filter(cursorline)
- self.cursorcolumn = to_filter(cursorcolumn)
-
- self.content = content or DummyControl()
- self.dont_extend_width = to_filter(dont_extend_width)
- self.dont_extend_height = to_filter(dont_extend_height)
- self.ignore_content_width = to_filter(ignore_content_width)
- self.ignore_content_height = to_filter(ignore_content_height)
- self.left_margins = left_margins or []
- self.right_margins = right_margins or []
- self.scroll_offsets = scroll_offsets or ScrollOffsets()
- self.get_vertical_scroll = get_vertical_scroll
- self.get_horizontal_scroll = get_horizontal_scroll
- self.colorcolumns = colorcolumns or []
- self.align = align
- self.style = style
- self.char = char
- self.get_line_prefix = get_line_prefix
-
- self.width = width
- self.height = height
- self.z_index = z_index
-
- # Cache for the screens generated by the margin.
- self._ui_content_cache: SimpleCache[
- Tuple[int, int, int], UIContent
- ] = SimpleCache(maxsize=8)
- self._margin_width_cache: SimpleCache[Tuple[Margin, int], int] = SimpleCache(
- maxsize=1
- )
-
- self.reset()
-
- def __repr__(self) -> str:
- return "Window(content=%r)" % self.content
-
- def reset(self) -> None:
- 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: Optional[WindowRenderInfo] = None
-
- def _get_margin_width(self, margin: Margin) -> int:
- """
- Return the width for this margin.
- (Calculate only once per render time.)
- """
- # Margin.get_width, needs to have a UIContent instance.
- def get_ui_content() -> UIContent:
- return self._get_ui_content(width=0, height=0)
-
- def get_width() -> int:
- return margin.get_width(get_ui_content)
-
- key = (margin, get_app().render_counter)
- return self._margin_width_cache.get(key, get_width)
-
- def _get_total_margin_width(self) -> int:
- """
- Calculate and return the width of the margin (left + right).
- """
- return sum(self._get_margin_width(m) for m in self.left_margins) + sum(
- self._get_margin_width(m) for m in self.right_margins
- )
-
- def preferred_width(self, max_available_width: int) -> Dimension:
- """
- Calculate the preferred width for this window.
- """
-
- def preferred_content_width() -> Optional[int]:
- """Content width: is only calculated if no exact width for the
- window was given."""
- if self.ignore_content_width():
- return None
-
- # Calculate the width of the margin.
- total_margin_width = self._get_total_margin_width()
-
- # Window of the content. (Can be `None`.)
- preferred_width = self.content.preferred_width(
- max_available_width - total_margin_width
- )
-
- if preferred_width is not None:
- # Include width of the margins.
- preferred_width += total_margin_width
- return preferred_width
-
- # Merge.
- return self._merge_dimensions(
- dimension=to_dimension(self.width),
- get_preferred=preferred_content_width,
- dont_extend=self.dont_extend_width(),
- )
-
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- """
- Calculate the preferred height for this window.
- """
-
- def preferred_content_height() -> Optional[int]:
- """Content height: is only calculated if no exact height for the
- window was given."""
- if self.ignore_content_height():
- return None
-
- total_margin_width = self._get_total_margin_width()
- wrap_lines = self.wrap_lines()
-
- return self.content.preferred_height(
- width - total_margin_width,
- max_available_height,
- wrap_lines,
- self.get_line_prefix,
- )
-
- return self._merge_dimensions(
- dimension=to_dimension(self.height),
- get_preferred=preferred_content_height,
- dont_extend=self.dont_extend_height(),
- )
-
- @staticmethod
- def _merge_dimensions(
- dimension: Optional[Dimension],
- get_preferred: Callable[[], Optional[int]],
- dont_extend: bool = False,
- ) -> Dimension:
- """
- Take the Dimension from this `Window` class and the received preferred
- size from the `UIControl` and return a `Dimension` to report to the
- parent container.
- """
- dimension = dimension or Dimension()
-
- # When a preferred dimension was explicitly given to the Window,
- # ignore the UIControl.
- preferred: Optional[int]
-
- if dimension.preferred_specified:
- preferred = dimension.preferred
- else:
- # Otherwise, calculate the preferred dimension from the UI control
- # content.
- preferred = get_preferred()
-
- # When a 'preferred' dimension is given by the UIControl, make sure
- # that it stays within the bounds of the Window.
- if preferred is not None:
- if dimension.max_specified:
- preferred = min(preferred, dimension.max)
-
- if dimension.min_specified:
- preferred = max(preferred, dimension.min)
-
- # When a `dont_extend` flag has been given, use the preferred dimension
- # also as the max dimension.
- max_: Optional[int]
- min_: Optional[int]
-
- if dont_extend and preferred is not None:
- max_ = min(dimension.max, preferred)
- else:
- max_ = dimension.max if dimension.max_specified else None
-
- min_ = dimension.min if dimension.min_specified else None
-
- return Dimension(
- min=min_, max=max_, preferred=preferred, weight=dimension.weight
- )
-
- def _get_ui_content(self, width: int, height: int) -> UIContent:
- """
- Create a `UIContent` instance.
- """
-
- def get_content() -> UIContent:
- return self.content.create_content(width=width, height=height)
-
- key = (get_app().render_counter, width, height)
- return self._ui_content_cache.get(key, get_content)
-
- def _get_digraph_char(self) -> Optional[str]:
- "Return `False`, or the Digraph symbol to be used."
- app = get_app()
- if app.quoted_insert:
- return "^"
- if app.vi_state.waiting_for_digraph:
- if app.vi_state.digraph_symbol1:
- return app.vi_state.digraph_symbol1
- return "?"
- return None
-
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- """
- Write window to screen. This renders the user control, the margins and
- copies everything over to the absolute position at the given screen.
- """
- # If dont_extend_width/height was given. Then reduce width/height in
- # WritePosition if the parent wanted us to paint in a bigger area.
- # (This happens if this window is bundled with another window in a
- # HSplit/VSplit, but with different size requirements.)
- write_position = WritePosition(
- xpos=write_position.xpos,
- ypos=write_position.ypos,
- width=write_position.width,
- height=write_position.height,
- )
-
- if self.dont_extend_width():
- write_position.width = min(
- write_position.width,
- self.preferred_width(write_position.width).preferred,
- )
-
- if self.dont_extend_height():
- write_position.height = min(
- write_position.height,
- self.preferred_height(
- write_position.width, write_position.height
- ).preferred,
- )
-
- # Draw
- z_index = z_index if self.z_index is None else self.z_index
-
- draw_func = partial(
- self._write_to_screen_at_index,
- screen,
- mouse_handlers,
- write_position,
- parent_style,
- erase_bg,
- )
-
- if z_index is None or z_index <= 0:
- # When no z_index is given, draw right away.
- draw_func()
- else:
- # Otherwise, postpone.
- screen.draw_with_z_index(z_index=z_index, draw_func=draw_func)
-
- def _write_to_screen_at_index(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- ) -> None:
- # Don't bother writing invisible windows.
- # (We save some time, but also avoid applying last-line styling.)
- if write_position.height <= 0 or write_position.width <= 0:
- return
-
- # Calculate margin sizes.
- left_margin_widths = [self._get_margin_width(m) for m in self.left_margins]
- right_margin_widths = [self._get_margin_width(m) for m in self.right_margins]
- total_margin_width = sum(left_margin_widths + right_margin_widths)
-
- # Render UserControl.
- ui_content = self.content.create_content(
- write_position.width - total_margin_width, write_position.height
- )
- assert isinstance(ui_content, UIContent)
-
- # Scroll content.
- wrap_lines = self.wrap_lines()
- self._scroll(
- ui_content, write_position.width - total_margin_width, write_position.height
- )
-
- # Erase background and fill with `char`.
- self._fill_bg(screen, write_position, erase_bg)
-
- # Resolve `align` attribute.
- align = self.align() if callable(self.align) else self.align
-
- # Write body
- visible_line_to_row_col, rowcol_to_yx = self._copy_body(
- ui_content,
- screen,
- write_position,
- sum(left_margin_widths),
- write_position.width - total_margin_width,
- self.vertical_scroll,
- self.horizontal_scroll,
- wrap_lines=wrap_lines,
- highlight_lines=True,
- vertical_scroll_2=self.vertical_scroll_2,
- always_hide_cursor=self.always_hide_cursor(),
- has_focus=get_app().layout.current_control == self.content,
- align=align,
- get_line_prefix=self.get_line_prefix,
- )
-
- # Remember render info. (Set before generating the margins. They need this.)
- x_offset = write_position.xpos + sum(left_margin_widths)
- y_offset = write_position.ypos
-
- render_info = WindowRenderInfo(
- window=self,
- ui_content=ui_content,
- horizontal_scroll=self.horizontal_scroll,
- vertical_scroll=self.vertical_scroll,
- window_width=write_position.width - total_margin_width,
- window_height=write_position.height,
- configured_scroll_offsets=self.scroll_offsets,
- visible_line_to_row_col=visible_line_to_row_col,
- rowcol_to_yx=rowcol_to_yx,
- x_offset=x_offset,
- y_offset=y_offset,
- wrap_lines=wrap_lines,
- )
- self.render_info = render_info
-
- # Set mouse handlers.
- def mouse_handler(mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- Wrapper around the mouse_handler of the `UIControl` that turns
- screen coordinates into line coordinates.
- Returns `NotImplemented` if no UI invalidation should be done.
- """
- # Don't handle mouse events outside of the current modal part of
- # the UI.
- if self not in get_app().layout.walk_through_modal_area():
- return NotImplemented
-
- # Find row/col position first.
- yx_to_rowcol = {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)
- result: NotImplementedOrNone
-
- while x >= 0:
- try:
- row, col = yx_to_rowcol[y, x]
- except KeyError:
- # Try again. (When clicking on the right side of double
- # width characters, or on the right side of the input.)
- x -= 1
- else:
- # Found position, call handler of UIControl.
- result = self.content.mouse_handler(
- MouseEvent(
- position=Point(x=col, y=row),
- event_type=mouse_event.event_type,
- button=mouse_event.button,
- modifiers=mouse_event.modifiers,
- )
- )
- break
- else:
- # nobreak.
- # (No x/y coordinate found for the content. This happens in
- # case of a DummyControl, that does not have any content.
- # Report (0,0) instead.)
- result = self.content.mouse_handler(
- MouseEvent(
- position=Point(x=0, y=0),
- event_type=mouse_event.event_type,
- button=mouse_event.button,
- modifiers=mouse_event.modifiers,
- )
- )
-
- # If it returns NotImplemented, handle it here.
- if result == NotImplemented:
- result = self._mouse_handler(mouse_event)
-
- return result
-
- mouse_handlers.set_mouse_handler_for_range(
- x_min=write_position.xpos + sum(left_margin_widths),
- x_max=write_position.xpos + write_position.width - total_margin_width,
- y_min=write_position.ypos,
- y_max=write_position.ypos + write_position.height,
- handler=mouse_handler,
- )
-
- # Render and copy margins.
- move_x = 0
-
- def render_margin(m: Margin, width: int) -> UIContent:
- "Render margin. Return `Screen`."
- # Retrieve margin fragments.
- fragments = m.create_margin(render_info, width, write_position.height)
-
- # Turn it into a UIContent object.
- # already rendered those fragments using this size.)
- return FormattedTextControl(fragments).create_content(
- width + 1, write_position.height
- )
-
- for m, width in zip(self.left_margins, left_margin_widths):
- if width > 0: # (ConditionalMargin returns a zero width. -- Don't render.)
- # Create screen for margin.
- margin_content = render_margin(m, width)
-
- # Copy and shift X.
- self._copy_margin(margin_content, 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_content = render_margin(m, width)
-
- # Copy and shift X.
- self._copy_margin(margin_content, screen, write_position, move_x, width)
- move_x += width
-
- # Apply 'self.style'
- self._apply_style(screen, write_position, parent_style)
-
- # Tell the screen that this user control has been painted at this
- # position.
- screen.visible_windows_to_write_positions[self] = write_position
-
- def _copy_body(
- self,
- ui_content: UIContent,
- new_screen: Screen,
- write_position: WritePosition,
- move_x: int,
- width: int,
- vertical_scroll: int = 0,
- horizontal_scroll: int = 0,
- wrap_lines: bool = False,
- highlight_lines: bool = False,
- vertical_scroll_2: int = 0,
- always_hide_cursor: bool = False,
- has_focus: bool = False,
- align: WindowAlign = WindowAlign.LEFT,
- get_line_prefix: Optional[Callable[[int, int], AnyFormattedText]] = None,
- ) -> Tuple[Dict[int, Tuple[int, int]], Dict[Tuple[int, int], Tuple[int, int]]]:
- """
- Copy the UIContent into the output screen.
- Return (visible_line_to_row_col, rowcol_to_yx) tuple.
-
- :param get_line_prefix: None or a callable that takes a line number
- (int) and a wrap_count (int) and returns formatted text.
- """
- xpos = write_position.xpos + move_x
- ypos = write_position.ypos
- line_count = ui_content.line_count
- new_buffer = new_screen.data_buffer
- empty_char = _CHAR_CACHE["", ""]
-
- # Map visible line number to (row, col) of input.
- # 'col' will always be zero if line wrapping is off.
- visible_line_to_row_col: Dict[int, Tuple[int, int]] = {}
-
- # Maps (row, col) from the input to (y, x) screen coordinates.
- rowcol_to_yx: Dict[Tuple[int, int], Tuple[int, int]] = {}
-
- def copy_line(
- line: StyleAndTextTuples,
- lineno: int,
- x: int,
- y: int,
- is_input: bool = False,
- ) -> Tuple[int, int]:
- """
- Copy over a single line to the output screen. This can wrap over
- multiple lines in the output. It will call the prefix (prompt)
- function before every line.
- """
- if is_input:
- current_rowcol_to_yx = rowcol_to_yx
- else:
- current_rowcol_to_yx = {} # Throwaway dictionary.
-
- # Draw line prefix.
- if is_input and get_line_prefix:
- prompt = to_formatted_text(get_line_prefix(lineno, 0))
- x, y = copy_line(prompt, lineno, x, y, is_input=False)
-
- # Scroll horizontally.
- skipped = 0 # Characters skipped because of horizontal scrolling.
- if horizontal_scroll and is_input:
- h_scroll = horizontal_scroll
- line = explode_text_fragments(line)
- while h_scroll > 0 and line:
- h_scroll -= get_cwidth(line[0][1])
- skipped += 1
- del line[:1] # Remove first character.
-
- x -= h_scroll # When scrolling over double width character,
- # this can end up being negative.
-
- # Align this line. (Note that this doesn't work well when we use
- # get_line_prefix and that function returns variable width prefixes.)
- if align == WindowAlign.CENTER:
- line_width = fragment_list_width(line)
- if line_width < width:
- x += (width - line_width) // 2
- elif align == WindowAlign.RIGHT:
- line_width = fragment_list_width(line)
- if line_width < width:
- x += width - line_width
-
- col = 0
- wrap_count = 0
- for style, text, *_ in line:
- new_buffer_row = new_buffer[y + ypos]
-
- # Remember raw VT escape sequences. (E.g. FinalTerm's
- # escape sequences.)
- if "[ZeroWidthEscape]" in style:
- new_screen.zero_width_escapes[y + ypos][x + xpos] += text
- continue
-
- for c in text:
- char = _CHAR_CACHE[c, style]
- char_width = char.width
-
- # Wrap when the line width is exceeded.
- if wrap_lines and x + char_width > width:
- visible_line_to_row_col[y + 1] = (
- lineno,
- visible_line_to_row_col[y][1] + x,
- )
- y += 1
- wrap_count += 1
- x = 0
-
- # Insert line prefix (continuation prompt).
- if is_input and get_line_prefix:
- prompt = to_formatted_text(
- get_line_prefix(lineno, wrap_count)
- )
- x, y = copy_line(prompt, lineno, x, y, is_input=False)
-
- new_buffer_row = new_buffer[y + ypos]
-
- if y >= write_position.height:
- return x, y # Break out of all for loops.
-
- # Set character in screen and shift 'x'.
- if x >= 0 and y >= 0 and x < width:
- new_buffer_row[x + xpos] = char
-
- # When we print a multi width character, make sure
- # to erase the neighbours positions in the screen.
- # (The empty string if different from everything,
- # so next redraw this cell will repaint anyway.)
- if char_width > 1:
- for i in range(1, char_width):
- new_buffer_row[x + xpos + i] = empty_char
-
- # If this is a zero width characters, then it's
- # probably part of a decomposed unicode character.
- # See: https://en.wikipedia.org/wiki/Unicode_equivalence
- # Merge it in the previous cell.
- elif char_width == 0:
- # Handle all character widths. If the previous
- # character is a multiwidth character, then
- # merge it two positions back.
- for pw in [2, 1]: # Previous character width.
- if (
- x - pw >= 0
- and new_buffer_row[x + xpos - pw].width == pw
- ):
- prev_char = new_buffer_row[x + xpos - pw]
- char2 = _CHAR_CACHE[
- prev_char.char + c, prev_char.style
- ]
- new_buffer_row[x + xpos - pw] = char2
-
- # Keep track of write position for each character.
- current_rowcol_to_yx[lineno, col + skipped] = (
- y + ypos,
- x + xpos,
- )
-
- col += 1
- x += char_width
- return x, y
-
- # Copy content.
- def copy() -> int:
- y = -vertical_scroll_2
- lineno = vertical_scroll
-
- while y < write_position.height and lineno < line_count:
- # Take the next line and copy it in the real screen.
- line = ui_content.get_line(lineno)
-
- visible_line_to_row_col[y] = (lineno, horizontal_scroll)
-
- # Copy margin and actual line.
- x = 0
- x, y = copy_line(line, lineno, x, y, is_input=True)
-
- lineno += 1
- y += 1
- return y
-
- copy()
-
- def cursor_pos_to_screen_pos(row: int, col: int) -> Point:
- "Translate row/col from UIContent to real Screen coordinates."
- try:
- y, x = rowcol_to_yx[row, col]
- except KeyError:
- # Normally this should never happen. (It is a bug, if it happens.)
- # But to be sure, return (0, 0)
- return Point(x=0, y=0)
-
- # raise ValueError(
- # 'Invalid position. row=%r col=%r, vertical_scroll=%r, '
- # 'horizontal_scroll=%r, height=%r' %
- # (row, col, vertical_scroll, horizontal_scroll, write_position.height))
- else:
- return Point(x=x, y=y)
-
- # Set cursor and menu positions.
- if ui_content.cursor_position:
- screen_cursor_position = cursor_pos_to_screen_pos(
- ui_content.cursor_position.y, ui_content.cursor_position.x
- )
-
- if has_focus:
- new_screen.set_cursor_position(self, screen_cursor_position)
-
- if always_hide_cursor:
- new_screen.show_cursor = False
- else:
- new_screen.show_cursor = ui_content.show_cursor
-
- self._highlight_digraph(new_screen)
-
- if highlight_lines:
- self._highlight_cursorlines(
- new_screen,
- screen_cursor_position,
- xpos,
- ypos,
- width,
- write_position.height,
- )
-
- # Draw input characters from the input processor queue.
- if has_focus and ui_content.cursor_position:
- self._show_key_processor_key_buffer(new_screen)
-
- # Set menu position.
- if ui_content.menu_position:
- new_screen.set_menu_position(
- self,
- cursor_pos_to_screen_pos(
- ui_content.menu_position.y, ui_content.menu_position.x
- ),
- )
-
- # Update output screen height.
- new_screen.height = max(new_screen.height, ypos + write_position.height)
-
- return visible_line_to_row_col, rowcol_to_yx
-
- def _fill_bg(
- self, screen: Screen, write_position: WritePosition, erase_bg: bool
- ) -> None:
- """
- Erase/fill the background.
- (Useful for floats and when a `char` has been given.)
- """
- char: Optional[str]
- if callable(self.char):
- char = self.char()
- else:
- char = self.char
-
- if erase_bg or char:
- wp = write_position
- char_obj = _CHAR_CACHE[char or " ", ""]
-
- for y in range(wp.ypos, wp.ypos + wp.height):
- row = screen.data_buffer[y]
- for x in range(wp.xpos, wp.xpos + wp.width):
- row[x] = char_obj
-
- def _apply_style(
- self, new_screen: Screen, write_position: WritePosition, parent_style: str
- ) -> None:
-
- # Apply `self.style`.
- style = parent_style + " " + to_str(self.style)
-
- new_screen.fill_area(write_position, style=style, after=False)
-
- # Apply the 'last-line' class to the last line of each Window. This can
- # be used to apply an 'underline' to the user control.
- wp = WritePosition(
- write_position.xpos,
- write_position.ypos + write_position.height - 1,
- write_position.width,
- 1,
- )
- new_screen.fill_area(wp, "class:last-line", after=True)
-
- def _highlight_digraph(self, new_screen: Screen) -> None:
- """
- When we are in Vi digraph mode, put a question mark underneath the
- cursor.
- """
- digraph_char = self._get_digraph_char()
- if digraph_char:
- cpos = new_screen.get_cursor_position(self)
- new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[
- digraph_char, "class:digraph"
- ]
-
- def _show_key_processor_key_buffer(self, new_screen: Screen) -> None:
- """
- When the user is typing a key binding that consists of several keys,
- display the last pressed key if the user is in insert mode and the key
- is meaningful to be displayed.
- E.g. Some people want to bind 'jj' to escape in Vi insert mode. But the
- first 'j' needs to be displayed in order to get some feedback.
- """
- app = get_app()
- key_buffer = app.key_processor.key_buffer
-
- if key_buffer and _in_insert_mode() and not app.is_done:
- # The textual data for the given key. (Can be a VT100 escape
- # sequence.)
- data = key_buffer[-1].data
-
- # Display only if this is a 1 cell width character.
- if get_cwidth(data) == 1:
- cpos = new_screen.get_cursor_position(self)
- new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[
- data, "class:partial-key-binding"
- ]
-
- def _highlight_cursorlines(
- self, new_screen: Screen, cpos: Point, x: int, y: int, width: int, height: int
- ) -> None:
- """
- Highlight cursor row/column.
- """
- cursor_line_style = " class:cursor-line "
- cursor_column_style = " class:cursor-column "
-
- data_buffer = new_screen.data_buffer
-
- # Highlight cursor line.
- if self.cursorline():
- row = data_buffer[cpos.y]
- for x in range(x, x + width):
- original_char = row[x]
- row[x] = _CHAR_CACHE[
- original_char.char, original_char.style + cursor_line_style
- ]
-
- # Highlight cursor column.
- if self.cursorcolumn():
- for y2 in range(y, y + height):
- row = data_buffer[y2]
- original_char = row[cpos.x]
- row[cpos.x] = _CHAR_CACHE[
- original_char.char, original_char.style + cursor_column_style
- ]
-
- # Highlight color columns
- colorcolumns = self.colorcolumns
- if callable(colorcolumns):
- colorcolumns = colorcolumns()
-
- for cc in colorcolumns:
- assert isinstance(cc, ColorColumn)
- column = cc.position
-
- if column < x + width: # Only draw when visible.
- color_column_style = " " + cc.style
-
- for y2 in range(y, y + height):
- row = data_buffer[y2]
- original_char = row[column + x]
- row[column + x] = _CHAR_CACHE[
- original_char.char, original_char.style + color_column_style
- ]
-
- def _copy_margin(
- self,
- margin_content: UIContent,
- new_screen: Screen,
- write_position: WritePosition,
- move_x: int,
- width: int,
- ) -> None:
- """
- 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(margin_content, new_screen, margin_write_position, 0, width)
-
- def _scroll(self, ui_content: UIContent, width: int, height: int) -> None:
- """
- Scroll body. Ensure that the cursor is visible.
- """
- if self.wrap_lines():
- func = self._scroll_when_linewrapping
- else:
- func = self._scroll_without_linewrapping
-
- func(ui_content, width, height)
-
- def _scroll_when_linewrapping(
- self, ui_content: UIContent, width: int, height: int
- ) -> None:
- """
- Scroll to make sure the cursor position is visible and that we maintain
- the requested scroll offset.
-
- Set `self.horizontal_scroll/vertical_scroll`.
- """
- scroll_offsets_bottom = self.scroll_offsets.bottom
- scroll_offsets_top = self.scroll_offsets.top
-
- # We don't have horizontal scrolling.
- self.horizontal_scroll = 0
-
- def get_line_height(lineno: int) -> int:
- return ui_content.get_height_for_line(lineno, width, self.get_line_prefix)
-
- # When there is no space, reset `vertical_scroll_2` to zero and abort.
- # This can happen if the margin is bigger than the window width.
- # Otherwise the text height will become "infinite" (a big number) and
- # the copy_line will spend a huge amount of iterations trying to render
- # nothing.
- if width <= 0:
- self.vertical_scroll = ui_content.cursor_position.y
- self.vertical_scroll_2 = 0
- return
-
- # If the current line consumes more than the whole window height,
- # then we have to scroll vertically inside this line. (We don't take
- # the scroll offsets into account for this.)
- # Also, ignore the scroll offsets in this case. Just set the vertical
- # scroll to this line.
- line_height = get_line_height(ui_content.cursor_position.y)
- if line_height > height - scroll_offsets_top:
- # Calculate the height of the text before the cursor (including
- # line prefixes).
- text_before_height = ui_content.get_height_for_line(
- ui_content.cursor_position.y,
- width,
- self.get_line_prefix,
- slice_stop=ui_content.cursor_position.x,
- )
-
- # Adjust scroll offset.
- self.vertical_scroll = ui_content.cursor_position.y
- self.vertical_scroll_2 = min(
- text_before_height - 1, # Keep the cursor visible.
- line_height
- - height, # Avoid blank lines at the bottom when scolling up again.
- self.vertical_scroll_2,
- )
- self.vertical_scroll_2 = max(
- 0, text_before_height - height, self.vertical_scroll_2
- )
- return
- else:
- self.vertical_scroll_2 = 0
-
- # Current line doesn't consume the whole height. Take scroll offsets into account.
- def get_min_vertical_scroll() -> int:
- # Make sure that the cursor line is not below the bottom.
- # (Calculate how many lines can be shown between the cursor and the .)
- used_height = 0
- prev_lineno = ui_content.cursor_position.y
-
- for lineno in range(ui_content.cursor_position.y, -1, -1):
- used_height += get_line_height(lineno)
-
- if used_height > height - scroll_offsets_bottom:
- return prev_lineno
- else:
- prev_lineno = lineno
- return 0
-
- def get_max_vertical_scroll() -> int:
- # Make sure that the cursor line is not above the top.
- prev_lineno = ui_content.cursor_position.y
- used_height = 0
-
- for lineno in range(ui_content.cursor_position.y - 1, -1, -1):
- used_height += get_line_height(lineno)
-
- if used_height > scroll_offsets_top:
- return prev_lineno
- else:
- prev_lineno = lineno
- return prev_lineno
-
- def get_topmost_visible() -> int:
- """
- Calculate the upper most line that can be visible, while the bottom
- is still visible. We should not allow scroll more than this if
- `allow_scroll_beyond_bottom` is false.
- """
- prev_lineno = ui_content.line_count - 1
- used_height = 0
- for lineno in range(ui_content.line_count - 1, -1, -1):
- used_height += get_line_height(lineno)
- if used_height > height:
- return prev_lineno
- else:
- prev_lineno = lineno
- return prev_lineno
-
- # Scroll vertically. (Make sure that the whole line which contains the
- # cursor is visible.
- topmost_visible = get_topmost_visible()
-
- # Note: the `min(topmost_visible, ...)` is to make sure that we
- # don't require scrolling up because of the bottom scroll offset,
- # when we are at the end of the document.
- self.vertical_scroll = max(
- self.vertical_scroll, min(topmost_visible, get_min_vertical_scroll())
- )
- self.vertical_scroll = min(self.vertical_scroll, get_max_vertical_scroll())
-
- # Disallow scrolling beyond bottom?
- if not self.allow_scroll_beyond_bottom():
- self.vertical_scroll = min(self.vertical_scroll, topmost_visible)
-
- def _scroll_without_linewrapping(
- self, ui_content: UIContent, width: int, height: int
- ) -> None:
- """
- Scroll to make sure the cursor position is visible and that we maintain
- the requested scroll offset.
-
- Set `self.horizontal_scroll/vertical_scroll`.
- """
- cursor_position = ui_content.cursor_position or Point(x=0, y=0)
-
- # Without line wrapping, we will never have to scroll vertically inside
- # a single line.
- self.vertical_scroll_2 = 0
-
- if ui_content.line_count == 0:
- self.vertical_scroll = 0
- self.horizontal_scroll = 0
- return
- else:
- current_line_text = fragment_list_to_text(
- ui_content.get_line(cursor_position.y)
- )
-
- def do_scroll(
- current_scroll: int,
- scroll_offset_start: int,
- scroll_offset_end: int,
- cursor_pos: int,
- window_size: int,
- content_size: int,
- ) -> int:
- "Scrolling algorithm. Used for both horizontal and vertical scrolling."
- # Calculate the scroll offset to apply.
- # This can obviously never be more than have the screen size. Also, when the
- # cursor appears at the top or bottom, we don't apply the offset.
- scroll_offset_start = int(
- min(scroll_offset_start, window_size / 2, cursor_pos)
- )
- scroll_offset_end = int(
- min(scroll_offset_end, window_size / 2, content_size - 1 - cursor_pos)
- )
-
- # Prevent negative scroll offsets.
- if current_scroll < 0:
- current_scroll = 0
-
- # Scroll back if we scrolled to much and there's still space to show more of the document.
- if (
- not self.allow_scroll_beyond_bottom()
- and current_scroll > content_size - window_size
- ):
- current_scroll = max(0, content_size - window_size)
-
- # Scroll up if cursor is before visible part.
- if current_scroll > cursor_pos - scroll_offset_start:
- current_scroll = max(0, cursor_pos - scroll_offset_start)
-
- # Scroll down if cursor is after visible part.
- if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end:
- current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end
-
- return current_scroll
-
- # When a preferred scroll is given, take that first into account.
- if self.get_vertical_scroll:
- self.vertical_scroll = self.get_vertical_scroll(self)
- assert isinstance(self.vertical_scroll, int)
- if self.get_horizontal_scroll:
- self.horizontal_scroll = self.get_horizontal_scroll(self)
- assert isinstance(self.horizontal_scroll, int)
-
- # Update horizontal/vertical scroll to make sure that the cursor
- # remains visible.
- offsets = self.scroll_offsets
-
- self.vertical_scroll = do_scroll(
- current_scroll=self.vertical_scroll,
- scroll_offset_start=offsets.top,
- scroll_offset_end=offsets.bottom,
- cursor_pos=ui_content.cursor_position.y,
- window_size=height,
- content_size=ui_content.line_count,
- )
-
- if self.get_line_prefix:
- current_line_prefix_width = fragment_list_width(
- to_formatted_text(self.get_line_prefix(ui_content.cursor_position.y, 0))
- )
- else:
- current_line_prefix_width = 0
-
- self.horizontal_scroll = do_scroll(
- current_scroll=self.horizontal_scroll,
- scroll_offset_start=offsets.left,
- scroll_offset_end=offsets.right,
- cursor_pos=get_cwidth(current_line_text[: ui_content.cursor_position.x]),
- window_size=width - current_line_prefix_width,
- # We can only analyse the current line. Calculating the width off
- # all the lines is too expensive.
- content_size=max(
- get_cwidth(current_line_text), self.horizontal_scroll + width
- ),
- )
-
- def _mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- Mouse handler. Called when the UI control doesn't handle this
- particular event.
-
- Return `NotImplemented` if nothing was done as a consequence of this
- key binding (no UI invalidate required in that case).
- """
- if mouse_event.event_type == MouseEventType.SCROLL_DOWN:
- self._scroll_down()
- return None
- elif mouse_event.event_type == MouseEventType.SCROLL_UP:
- self._scroll_up()
- return None
-
- return NotImplemented
-
- def _scroll_down(self) -> None:
- "Scroll window down."
- info = self.render_info
-
- if info is None:
- return
-
- if self.vertical_scroll < info.content_height - info.window_height:
- if info.cursor_position.y <= info.configured_scroll_offsets.top:
- self.content.move_cursor_down()
-
- self.vertical_scroll += 1
-
- def _scroll_up(self) -> None:
- "Scroll window up."
- info = self.render_info
-
- if info is None:
- return
-
- if info.vertical_scroll > 0:
- # TODO: not entirely correct yet in case of line wrapping and long lines.
- if (
- info.cursor_position.y
- >= info.window_height - 1 - info.configured_scroll_offsets.bottom
- ):
- self.content.move_cursor_up()
-
- self.vertical_scroll -= 1
-
- def get_key_bindings(self) -> Optional[KeyBindingsBase]:
- return self.content.get_key_bindings()
-
- def get_children(self) -> List[Container]:
- return []
-
-
-class ConditionalContainer(Container):
- """
- Wrapper around any other container that can change the visibility. The
- received `filter` determines whether the given container should be
- displayed or not.
-
- :param content: :class:`.Container` instance.
- :param filter: :class:`.Filter` instance.
- """
-
- def __init__(self, content: AnyContainer, filter: FilterOrBool) -> None:
- self.content = to_container(content)
- self.filter = to_filter(filter)
-
- def __repr__(self) -> str:
- return f"ConditionalContainer({self.content!r}, filter={self.filter!r})"
-
- def reset(self) -> None:
- self.content.reset()
-
- def preferred_width(self, max_available_width: int) -> Dimension:
- if self.filter():
- return self.content.preferred_width(max_available_width)
- else:
- return Dimension.zero()
-
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- if self.filter():
- return self.content.preferred_height(width, max_available_height)
- else:
- return Dimension.zero()
-
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- if self.filter():
- return self.content.write_to_screen(
- screen, mouse_handlers, write_position, parent_style, erase_bg, z_index
- )
-
- def get_children(self) -> List[Container]:
- return [self.content]
-
-
-class DynamicContainer(Container):
- """
- Container class that dynamically returns any Container.
-
- :param get_container: Callable that returns a :class:`.Container` instance
- or any widget with a ``__pt_container__`` method.
- """
-
- def __init__(self, get_container: Callable[[], AnyContainer]) -> None:
- self.get_container = get_container
-
- def _get_container(self) -> Container:
- """
- Return the current container object.
-
- We call `to_container`, because `get_container` can also return a
- widget with a ``__pt_container__`` method.
- """
- obj = self.get_container()
- return to_container(obj)
-
- def reset(self) -> None:
- self._get_container().reset()
-
- def preferred_width(self, max_available_width: int) -> Dimension:
- return self._get_container().preferred_width(max_available_width)
-
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- return self._get_container().preferred_height(width, max_available_height)
-
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- self._get_container().write_to_screen(
- screen, mouse_handlers, write_position, parent_style, erase_bg, z_index
- )
-
- def is_modal(self) -> bool:
- return False
-
- def get_key_bindings(self) -> Optional[KeyBindingsBase]:
- # Key bindings will be collected when `layout.walk()` finds the child
- # container.
- return None
-
- def get_children(self) -> List[Container]:
- # Here we have to return the current active container itself, not its
- # children. Otherwise, we run into issues where `layout.walk()` will
- # never see an object of type `Window` if this contains a window. We
- # can't/shouldn't proxy the "isinstance" check.
- return [self._get_container()]
-
-
-def to_container(container: AnyContainer) -> Container:
- """
- Make sure that the given object is a :class:`.Container`.
- """
- if isinstance(container, Container):
- return container
- elif hasattr(container, "__pt_container__"):
- return to_container(container.__pt_container__())
- else:
- raise ValueError(f"Not a container object: {container!r}")
-
-
-def to_window(container: AnyContainer) -> Window:
- """
- Make sure that the given argument is a :class:`.Window`.
- """
- if isinstance(container, Window):
- return container
- elif hasattr(container, "__pt_container__"):
- return to_window(cast("MagicContainer", container).__pt_container__())
- else:
- raise ValueError(f"Not a Window object: {container!r}.")
-
-
-def is_container(value: object) -> "TypeGuard[AnyContainer]":
- """
- Checks whether the given value is a container object
- (for use in assert statements).
- """
- if isinstance(value, Container):
- return True
- if hasattr(value, "__pt_container__"):
- return is_container(cast("MagicContainer", value).__pt_container__())
- return False
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py
deleted file mode 100644
index 016d2894665..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/controls.py
+++ /dev/null
@@ -1,957 +0,0 @@
-"""
-User interface Controls for the layout.
-"""
-import time
-from abc import ABCMeta, abstractmethod
-from typing import (
- TYPE_CHECKING,
- Callable,
- Dict,
- Hashable,
- Iterable,
- List,
- NamedTuple,
- Optional,
- Union,
-)
-
-from prompt_toolkit.application.current import get_app
-from prompt_toolkit.buffer import Buffer
-from prompt_toolkit.cache import SimpleCache
-from prompt_toolkit.data_structures import Point
-from prompt_toolkit.document import Document
-from prompt_toolkit.filters import FilterOrBool, to_filter
-from prompt_toolkit.formatted_text import (
- AnyFormattedText,
- StyleAndTextTuples,
- to_formatted_text,
-)
-from prompt_toolkit.formatted_text.utils import (
- fragment_list_to_text,
- fragment_list_width,
- split_lines,
-)
-from prompt_toolkit.lexers import Lexer, SimpleLexer
-from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType
-from prompt_toolkit.search import SearchState
-from prompt_toolkit.selection import SelectionType
-from prompt_toolkit.utils import get_cwidth
-
-from .processors import (
- DisplayMultipleCursors,
- HighlightIncrementalSearchProcessor,
- HighlightSearchProcessor,
- HighlightSelectionProcessor,
- Processor,
- TransformationInput,
- merge_processors,
-)
-
-if TYPE_CHECKING:
- from prompt_toolkit.key_binding.key_bindings import (
- KeyBindingsBase,
- NotImplementedOrNone,
- )
- from prompt_toolkit.utils import Event
-
-
-__all__ = [
- "BufferControl",
- "SearchBufferControl",
- "DummyControl",
- "FormattedTextControl",
- "UIControl",
- "UIContent",
-]
-
-GetLinePrefixCallable = Callable[[int, int], AnyFormattedText]
-
-
-class UIControl(metaclass=ABCMeta):
- """
- Base class for all user interface controls.
- """
-
- def reset(self) -> None:
- # Default reset. (Doesn't have to be implemented.)
- pass
-
- def preferred_width(self, max_available_width: int) -> Optional[int]:
- return None
-
- def preferred_height(
- self,
- width: int,
- max_available_height: int,
- wrap_lines: bool,
- get_line_prefix: Optional[GetLinePrefixCallable],
- ) -> Optional[int]:
- return None
-
- def is_focusable(self) -> bool:
- """
- Tell whether this user control is focusable.
- """
- return False
-
- @abstractmethod
- def create_content(self, width: int, height: int) -> "UIContent":
- """
- Generate the content for this user control.
-
- Returns a :class:`.UIContent` instance.
- """
-
- def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- Handle mouse events.
-
- When `NotImplemented` is returned, it means that the given event is not
- handled by the `UIControl` itself. The `Window` or key bindings can
- decide to handle this event as scrolling or changing focus.
-
- :param mouse_event: `MouseEvent` instance.
- """
- return NotImplemented
-
- def move_cursor_down(self) -> None:
- """
- Request to move the cursor down.
- This happens when scrolling down and the cursor is completely at the
- top.
- """
-
- def move_cursor_up(self) -> None:
- """
- Request to move the cursor up.
- """
-
- def get_key_bindings(self) -> Optional["KeyBindingsBase"]:
- """
- The key bindings that are specific for this user control.
-
- Return a :class:`.KeyBindings` object if some key bindings are
- specified, or `None` otherwise.
- """
-
- def get_invalidate_events(self) -> Iterable["Event[object]"]:
- """
- Return a list of `Event` objects. This can be a generator.
- (The application collects all these events, in order to bind redraw
- handlers to these events.)
- """
- return []
-
-
-class UIContent:
- """
- Content generated by a user control. This content consists of a list of
- lines.
-
- :param get_line: Callable that takes a line number and returns the current
- line. This is a list of (style_str, text) tuples.
- :param line_count: The number of lines.
- :param cursor_position: a :class:`.Point` for the cursor position.
- :param menu_position: a :class:`.Point` for the menu position.
- :param show_cursor: Make the cursor visible.
- """
-
- def __init__(
- self,
- get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []),
- line_count: int = 0,
- cursor_position: Optional[Point] = None,
- menu_position: Optional[Point] = None,
- show_cursor: bool = True,
- ):
-
- self.get_line = get_line
- self.line_count = line_count
- self.cursor_position = cursor_position or Point(x=0, y=0)
- self.menu_position = menu_position
- self.show_cursor = show_cursor
-
- # Cache for line heights. Maps cache key -> height
- self._line_heights_cache: Dict[Hashable, int] = {}
-
- def __getitem__(self, lineno: int) -> StyleAndTextTuples:
- "Make it iterable (iterate line by line)."
- if lineno < self.line_count:
- return self.get_line(lineno)
- else:
- raise IndexError
-
- def get_height_for_line(
- self,
- lineno: int,
- width: int,
- get_line_prefix: Optional[GetLinePrefixCallable],
- slice_stop: Optional[int] = None,
- ) -> int:
- """
- Return the height that a given line would need if it is rendered in a
- space with the given width (using line wrapping).
-
- :param get_line_prefix: None or a `Window.get_line_prefix` callable
- that returns the prefix to be inserted before this line.
- :param slice_stop: Wrap only "line[:slice_stop]" and return that
- partial result. This is needed for scrolling the window correctly
- when line wrapping.
- :returns: The computed height.
- """
- # Instead of using `get_line_prefix` as key, we use render_counter
- # instead. This is more reliable, because this function could still be
- # the same, while the content would change over time.
- key = get_app().render_counter, lineno, width, slice_stop
-
- try:
- return self._line_heights_cache[key]
- except KeyError:
- if width == 0:
- height = 10**8
- else:
- # Calculate line width first.
- line = fragment_list_to_text(self.get_line(lineno))[:slice_stop]
- text_width = get_cwidth(line)
-
- if get_line_prefix:
- # Add prefix width.
- text_width += fragment_list_width(
- to_formatted_text(get_line_prefix(lineno, 0))
- )
-
- # Slower path: compute path when there's a line prefix.
- height = 1
-
- # Keep wrapping as long as the line doesn't fit.
- # Keep adding new prefixes for every wrapped line.
- while text_width > width:
- height += 1
- text_width -= width
-
- fragments2 = to_formatted_text(
- get_line_prefix(lineno, height - 1)
- )
- prefix_width = get_cwidth(fragment_list_to_text(fragments2))
-
- if prefix_width >= width: # Prefix doesn't fit.
- height = 10**8
- break
-
- text_width += prefix_width
- else:
- # Fast path: compute height when there's no line prefix.
- try:
- quotient, remainder = divmod(text_width, width)
- except ZeroDivisionError:
- height = 10**8
- else:
- if remainder:
- quotient += 1 # Like math.ceil.
- height = max(1, quotient)
-
- # Cache and return
- self._line_heights_cache[key] = height
- return height
-
-
-class FormattedTextControl(UIControl):
- """
- Control that displays formatted text. This can be either plain text, an
- :class:`~prompt_toolkit.formatted_text.HTML` object an
- :class:`~prompt_toolkit.formatted_text.ANSI` object, a list of ``(style_str,
- text)`` tuples or a callable that takes no argument and returns one of
- those, depending on how you prefer to do the formatting. See
- ``prompt_toolkit.layout.formatted_text`` for more information.
-
- (It's mostly optimized for rather small widgets, like toolbars, menus, etc...)
-
- When this UI control has the focus, the cursor will be shown in the upper
- left corner of this control by default. There are two ways for specifying
- the cursor position:
-
- - Pass a `get_cursor_position` function which returns a `Point` instance
- with the current cursor position.
-
- - If the (formatted) text is passed as a list of ``(style, text)`` tuples
- and there is one that looks like ``('[SetCursorPosition]', '')``, then
- this will specify the cursor position.
-
- Mouse support:
-
- The list of fragments can also contain tuples of three items, looking like:
- (style_str, text, handler). When mouse support is enabled and the user
- clicks on this fragment, then the given handler is called. That handler
- should accept two inputs: (Application, MouseEvent) and it should
- either handle the event or return `NotImplemented` in case we want the
- containing Window to handle this event.
-
- :param focusable: `bool` or :class:`.Filter`: Tell whether this control is
- focusable.
-
- :param text: Text or formatted text to be displayed.
- :param style: Style string applied to the content. (If you want to style
- the whole :class:`~prompt_toolkit.layout.Window`, pass the style to the
- :class:`~prompt_toolkit.layout.Window` instead.)
- :param key_bindings: a :class:`.KeyBindings` object.
- :param get_cursor_position: A callable that returns the cursor position as
- a `Point` instance.
- """
-
- def __init__(
- self,
- text: AnyFormattedText = "",
- style: str = "",
- focusable: FilterOrBool = False,
- key_bindings: Optional["KeyBindingsBase"] = None,
- show_cursor: bool = True,
- modal: bool = False,
- get_cursor_position: Optional[Callable[[], Optional[Point]]] = None,
- ) -> None:
-
- self.text = text # No type check on 'text'. This is done dynamically.
- self.style = style
- self.focusable = to_filter(focusable)
-
- # Key bindings.
- self.key_bindings = key_bindings
- self.show_cursor = show_cursor
- self.modal = modal
- self.get_cursor_position = get_cursor_position
-
- #: Cache for the content.
- self._content_cache: SimpleCache[Hashable, UIContent] = SimpleCache(maxsize=18)
- self._fragment_cache: SimpleCache[int, StyleAndTextTuples] = SimpleCache(
- maxsize=1
- )
- # Only cache one fragment list. We don't need the previous item.
-
- # Render info for the mouse support.
- self._fragments: Optional[StyleAndTextTuples] = None
-
- def reset(self) -> None:
- self._fragments = None
-
- def is_focusable(self) -> bool:
- return self.focusable()
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.text!r})"
-
- def _get_formatted_text_cached(self) -> StyleAndTextTuples:
- """
- Get fragments, but only retrieve fragments once during one render run.
- (This function is called several times during one rendering, because
- we also need those for calculating the dimensions.)
- """
- return self._fragment_cache.get(
- get_app().render_counter, lambda: to_formatted_text(self.text, self.style)
- )
-
- def preferred_width(self, max_available_width: int) -> int:
- """
- Return the preferred width for this control.
- That is the width of the longest line.
- """
- text = fragment_list_to_text(self._get_formatted_text_cached())
- line_lengths = [get_cwidth(l) for l in text.split("\n")]
- return max(line_lengths)
-
- def preferred_height(
- self,
- width: int,
- max_available_height: int,
- wrap_lines: bool,
- get_line_prefix: Optional[GetLinePrefixCallable],
- ) -> Optional[int]:
- """
- Return the preferred height for this control.
- """
- content = self.create_content(width, None)
- if wrap_lines:
- height = 0
- for i in range(content.line_count):
- height += content.get_height_for_line(i, width, get_line_prefix)
- if height >= max_available_height:
- return max_available_height
- return height
- else:
- return content.line_count
-
- def create_content(self, width: int, height: Optional[int]) -> UIContent:
- # Get fragments
- fragments_with_mouse_handlers = self._get_formatted_text_cached()
- fragment_lines_with_mouse_handlers = list(
- split_lines(fragments_with_mouse_handlers)
- )
-
- # Strip mouse handlers from fragments.
- fragment_lines: List[StyleAndTextTuples] = [
- [(item[0], item[1]) for item in line]
- for line in fragment_lines_with_mouse_handlers
- ]
-
- # Keep track of the fragments with mouse handler, for later use in
- # `mouse_handler`.
- self._fragments = fragments_with_mouse_handlers
-
- # If there is a `[SetCursorPosition]` in the fragment list, set the
- # cursor position here.
- def get_cursor_position(
- fragment: str = "[SetCursorPosition]",
- ) -> Optional[Point]:
- for y, line in enumerate(fragment_lines):
- x = 0
- for style_str, text, *_ in line:
- if fragment in style_str:
- return Point(x=x, y=y)
- x += len(text)
- return None
-
- # If there is a `[SetMenuPosition]`, set the menu over here.
- def get_menu_position() -> Optional[Point]:
- return get_cursor_position("[SetMenuPosition]")
-
- cursor_position = (self.get_cursor_position or get_cursor_position)()
-
- # Create content, or take it from the cache.
- key = (tuple(fragments_with_mouse_handlers), width, cursor_position)
-
- def get_content() -> UIContent:
- return UIContent(
- get_line=lambda i: fragment_lines[i],
- line_count=len(fragment_lines),
- show_cursor=self.show_cursor,
- cursor_position=cursor_position,
- menu_position=get_menu_position(),
- )
-
- return self._content_cache.get(key, get_content)
-
- def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- Handle mouse events.
-
- (When the fragment list contained mouse handlers and the user clicked on
- on any of these, the matching handler is called. This handler can still
- return `NotImplemented` in case we want the
- :class:`~prompt_toolkit.layout.Window` to handle this particular
- event.)
- """
- if self._fragments:
- # Read the generator.
- fragments_for_line = list(split_lines(self._fragments))
-
- try:
- fragments = fragments_for_line[mouse_event.position.y]
- except IndexError:
- return NotImplemented
- else:
- # Find position in the fragment list.
- xpos = mouse_event.position.x
-
- # Find mouse handler for this character.
- count = 0
- for item in fragments:
- count += len(item[1])
- if count > xpos:
- if len(item) >= 3:
- # Handler found. Call it.
- # (Handler can return NotImplemented, so return
- # that result.)
- handler = item[2] # type: ignore
- return handler(mouse_event)
- else:
- break
-
- # Otherwise, don't handle here.
- return NotImplemented
-
- def is_modal(self) -> bool:
- return self.modal
-
- def get_key_bindings(self) -> Optional["KeyBindingsBase"]:
- return self.key_bindings
-
-
-class DummyControl(UIControl):
- """
- A dummy control object that doesn't paint any content.
-
- Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The
- `fragment` and `char` attributes of the `Window` class can be used to
- define the filling.)
- """
-
- def create_content(self, width: int, height: int) -> UIContent:
- def get_line(i: int) -> StyleAndTextTuples:
- return []
-
- return UIContent(
- get_line=get_line, line_count=100**100
- ) # Something very big.
-
- def is_focusable(self) -> bool:
- return False
-
-
-class _ProcessedLine(NamedTuple):
- fragments: StyleAndTextTuples
- source_to_display: Callable[[int], int]
- display_to_source: Callable[[int], int]
-
-
-class BufferControl(UIControl):
- """
- Control for visualising the content of a :class:`.Buffer`.
-
- :param buffer: The :class:`.Buffer` object to be displayed.
- :param input_processors: A list of
- :class:`~prompt_toolkit.layout.processors.Processor` objects.
- :param include_default_input_processors: When True, include the default
- processors for highlighting of selection, search and displaying of
- multiple cursors.
- :param lexer: :class:`.Lexer` instance for syntax highlighting.
- :param preview_search: `bool` or :class:`.Filter`: Show search while
- typing. When this is `True`, probably you want to add a
- ``HighlightIncrementalSearchProcessor`` as well. Otherwise only the
- cursor position will move, but the text won't be highlighted.
- :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable.
- :param focus_on_click: Focus this buffer when it's click, but not yet focused.
- :param key_bindings: a :class:`.KeyBindings` object.
- """
-
- def __init__(
- self,
- buffer: Optional[Buffer] = None,
- input_processors: Optional[List[Processor]] = None,
- include_default_input_processors: bool = True,
- lexer: Optional[Lexer] = None,
- preview_search: FilterOrBool = False,
- focusable: FilterOrBool = True,
- search_buffer_control: Union[
- None, "SearchBufferControl", Callable[[], "SearchBufferControl"]
- ] = None,
- menu_position: Optional[Callable[[], Optional[int]]] = None,
- focus_on_click: FilterOrBool = False,
- key_bindings: Optional["KeyBindingsBase"] = None,
- ):
-
- self.input_processors = input_processors
- self.include_default_input_processors = include_default_input_processors
-
- self.default_input_processors = [
- HighlightSearchProcessor(),
- HighlightIncrementalSearchProcessor(),
- HighlightSelectionProcessor(),
- DisplayMultipleCursors(),
- ]
-
- self.preview_search = to_filter(preview_search)
- self.focusable = to_filter(focusable)
- self.focus_on_click = to_filter(focus_on_click)
-
- self.buffer = buffer or Buffer()
- self.menu_position = menu_position
- self.lexer = lexer or SimpleLexer()
- self.key_bindings = key_bindings
- self._search_buffer_control = search_buffer_control
-
- #: Cache for the lexer.
- #: Often, due to cursor movement, undo/redo and window resizing
- #: operations, it happens that a short time, the same document has to be
- #: lexed. This is a fairly easy way to cache such an expensive operation.
- self._fragment_cache: SimpleCache[
- Hashable, Callable[[int], StyleAndTextTuples]
- ] = SimpleCache(maxsize=8)
-
- self._last_click_timestamp: Optional[float] = None
- self._last_get_processed_line: Optional[Callable[[int], _ProcessedLine]] = None
-
- def __repr__(self) -> str:
- return f"<{self.__class__.__name__} buffer={self.buffer!r} at {id(self)!r}>"
-
- @property
- def search_buffer_control(self) -> Optional["SearchBufferControl"]:
- result: Optional[SearchBufferControl]
-
- if callable(self._search_buffer_control):
- result = self._search_buffer_control()
- else:
- result = self._search_buffer_control
-
- assert result is None or isinstance(result, SearchBufferControl)
- return result
-
- @property
- def search_buffer(self) -> Optional[Buffer]:
- control = self.search_buffer_control
- if control is not None:
- return control.buffer
- return None
-
- @property
- def search_state(self) -> SearchState:
- """
- Return the `SearchState` for searching this `BufferControl`. This is
- always associated with the search control. If one search bar is used
- for searching multiple `BufferControls`, then they share the same
- `SearchState`.
- """
- search_buffer_control = self.search_buffer_control
- if search_buffer_control:
- return search_buffer_control.searcher_search_state
- else:
- return SearchState()
-
- def is_focusable(self) -> bool:
- return self.focusable()
-
- def preferred_width(self, max_available_width: int) -> Optional[int]:
- """
- This should return the preferred width.
-
- Note: We don't specify a preferred width according to the content,
- because it would be too expensive. Calculating the preferred
- width can be done by calculating the longest line, but this would
- require applying all the processors to each line. This is
- unfeasible for a larger document, and doing it for small
- documents only would result in inconsistent behaviour.
- """
- return None
-
- def preferred_height(
- self,
- width: int,
- max_available_height: int,
- wrap_lines: bool,
- get_line_prefix: Optional[GetLinePrefixCallable],
- ) -> Optional[int]:
-
- # Calculate the content height, if it was drawn on a screen with the
- # given width.
- height = 0
- content = self.create_content(width, height=1) # Pass a dummy '1' as height.
-
- # When line wrapping is off, the height should be equal to the amount
- # of lines.
- if not wrap_lines:
- return content.line_count
-
- # When the number of lines exceeds the max_available_height, just
- # return max_available_height. No need to calculate anything.
- if content.line_count >= max_available_height:
- return max_available_height
-
- for i in range(content.line_count):
- height += content.get_height_for_line(i, width, get_line_prefix)
-
- if height >= max_available_height:
- return max_available_height
-
- return height
-
- def _get_formatted_text_for_line_func(
- self, document: Document
- ) -> Callable[[int], StyleAndTextTuples]:
- """
- Create a function that returns the fragments for a given line.
- """
- # Cache using `document.text`.
- def get_formatted_text_for_line() -> Callable[[int], StyleAndTextTuples]:
- return self.lexer.lex_document(document)
-
- key = (document.text, self.lexer.invalidation_hash())
- return self._fragment_cache.get(key, get_formatted_text_for_line)
-
- def _create_get_processed_line_func(
- self, document: Document, width: int, height: int
- ) -> Callable[[int], _ProcessedLine]:
- """
- Create a function that takes a line number of the current document and
- returns a _ProcessedLine(processed_fragments, source_to_display, display_to_source)
- tuple.
- """
- # Merge all input processors together.
- input_processors = self.input_processors or []
- if self.include_default_input_processors:
- input_processors = self.default_input_processors + input_processors
-
- merged_processor = merge_processors(input_processors)
-
- def transform(lineno: int, fragments: StyleAndTextTuples) -> _ProcessedLine:
- "Transform the fragments for a given line number."
- # Get cursor position at this line.
- def source_to_display(i: int) -> int:
- """X position from the buffer to the x position in the
- processed fragment list. By default, we start from the 'identity'
- operation."""
- return i
-
- transformation = merged_processor.apply_transformation(
- TransformationInput(
- self, document, lineno, source_to_display, fragments, width, height
- )
- )
-
- return _ProcessedLine(
- transformation.fragments,
- transformation.source_to_display,
- transformation.display_to_source,
- )
-
- def create_func() -> Callable[[int], _ProcessedLine]:
- get_line = self._get_formatted_text_for_line_func(document)
- cache: Dict[int, _ProcessedLine] = {}
-
- def get_processed_line(i: int) -> _ProcessedLine:
- try:
- return cache[i]
- except KeyError:
- processed_line = transform(i, get_line(i))
- cache[i] = processed_line
- return processed_line
-
- return get_processed_line
-
- return create_func()
-
- def create_content(
- self, width: int, height: int, preview_search: bool = False
- ) -> UIContent:
- """
- Create a UIContent.
- """
- buffer = self.buffer
-
- # Trigger history loading of the buffer. We do this during the
- # rendering of the UI here, because it needs to happen when an
- # `Application` with its event loop is running. During the rendering of
- # the buffer control is the earliest place we can achieve this, where
- # we're sure the right event loop is active, and don't require user
- # interaction (like in a key binding).
- buffer.load_history_if_not_yet_loaded()
-
- # Get the document to be shown. If we are currently searching (the
- # search buffer has focus, and the preview_search filter is enabled),
- # then use the search document, which has possibly a different
- # text/cursor position.)
- search_control = self.search_buffer_control
- preview_now = preview_search or bool(
- # Only if this feature is enabled.
- self.preview_search()
- and
- # And something was typed in the associated search field.
- search_control
- and search_control.buffer.text
- and
- # And we are searching in this control. (Many controls can point to
- # the same search field, like in Pyvim.)
- get_app().layout.search_target_buffer_control == self
- )
-
- if preview_now and search_control is not None:
- ss = self.search_state
-
- document = buffer.document_for_search(
- SearchState(
- text=search_control.buffer.text,
- direction=ss.direction,
- ignore_case=ss.ignore_case,
- )
- )
- else:
- document = buffer.document
-
- get_processed_line = self._create_get_processed_line_func(
- document, width, height
- )
- self._last_get_processed_line = get_processed_line
-
- def translate_rowcol(row: int, col: int) -> Point:
- "Return the content column for this coordinate."
- return Point(x=get_processed_line(row).source_to_display(col), y=row)
-
- def get_line(i: int) -> StyleAndTextTuples:
- "Return the fragments for a given line number."
- fragments = get_processed_line(i).fragments
-
- # Add a space at the end, because that is a possible cursor
- # position. (When inserting after the input.) We should do this on
- # all the lines, not just the line containing the cursor. (Because
- # otherwise, line wrapping/scrolling could change when moving the
- # cursor around.)
- fragments = fragments + [("", " ")]
- return fragments
-
- content = UIContent(
- get_line=get_line,
- line_count=document.line_count,
- cursor_position=translate_rowcol(
- document.cursor_position_row, document.cursor_position_col
- ),
- )
-
- # If there is an auto completion going on, use that start point for a
- # pop-up menu position. (But only when this buffer has the focus --
- # there is only one place for a menu, determined by the focused buffer.)
- if get_app().layout.current_control == self:
- menu_position = self.menu_position() if self.menu_position else None
- if menu_position is not None:
- assert isinstance(menu_position, int)
- menu_row, menu_col = buffer.document.translate_index_to_position(
- menu_position
- )
- content.menu_position = translate_rowcol(menu_row, menu_col)
- elif buffer.complete_state:
- # Position for completion menu.
- # Note: We use 'min', because the original cursor position could be
- # behind the input string when the actual completion is for
- # some reason shorter than the text we had before. (A completion
- # can change and shorten the input.)
- menu_row, menu_col = buffer.document.translate_index_to_position(
- min(
- buffer.cursor_position,
- buffer.complete_state.original_document.cursor_position,
- )
- )
- content.menu_position = translate_rowcol(menu_row, menu_col)
- else:
- content.menu_position = None
-
- return content
-
- def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- Mouse handler for this control.
- """
- buffer = self.buffer
- position = mouse_event.position
-
- # Focus buffer when clicked.
- if get_app().layout.current_control == self:
- if self._last_get_processed_line:
- processed_line = self._last_get_processed_line(position.y)
-
- # Translate coordinates back to the cursor position of the
- # original input.
- xpos = processed_line.display_to_source(position.x)
- index = buffer.document.translate_row_col_to_index(position.y, xpos)
-
- # Set the cursor position.
- if mouse_event.event_type == MouseEventType.MOUSE_DOWN:
- buffer.exit_selection()
- buffer.cursor_position = index
-
- elif (
- mouse_event.event_type == MouseEventType.MOUSE_MOVE
- and mouse_event.button != MouseButton.NONE
- ):
- # Click and drag to highlight a selection
- if (
- buffer.selection_state is None
- and abs(buffer.cursor_position - index) > 0
- ):
- buffer.start_selection(selection_type=SelectionType.CHARACTERS)
- buffer.cursor_position = index
-
- elif mouse_event.event_type == MouseEventType.MOUSE_UP:
- # When the cursor was moved to another place, select the text.
- # (The >1 is actually a small but acceptable workaround for
- # selecting text in Vi navigation mode. In navigation mode,
- # the cursor can never be after the text, so the cursor
- # will be repositioned automatically.)
- if abs(buffer.cursor_position - index) > 1:
- if buffer.selection_state is None:
- 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 < 0.3
- )
- self._last_click_timestamp = time.time()
-
- if double_click:
- start, end = buffer.document.find_boundaries_of_current_word()
- buffer.cursor_position += start
- buffer.start_selection(selection_type=SelectionType.CHARACTERS)
- buffer.cursor_position += end - start
- else:
- # Don't handle scroll events here.
- return NotImplemented
-
- # Not focused, but focusing on click events.
- else:
- if (
- self.focus_on_click()
- and mouse_event.event_type == MouseEventType.MOUSE_UP
- ):
- # Focus happens on mouseup. (If we did this on mousedown, the
- # up event will be received at the point where this widget is
- # focused and be handled anyway.)
- get_app().layout.current_control = self
- else:
- return NotImplemented
-
- return None
-
- def move_cursor_down(self) -> None:
- b = self.buffer
- b.cursor_position += b.document.get_cursor_down_position()
-
- def move_cursor_up(self) -> None:
- b = self.buffer
- b.cursor_position += b.document.get_cursor_up_position()
-
- def get_key_bindings(self) -> Optional["KeyBindingsBase"]:
- """
- When additional key bindings are given. Return these.
- """
- return self.key_bindings
-
- def get_invalidate_events(self) -> Iterable["Event[object]"]:
- """
- Return the Window invalidate events.
- """
- # Whenever the buffer changes, the UI has to be updated.
- yield self.buffer.on_text_changed
- yield self.buffer.on_cursor_position_changed
-
- yield self.buffer.on_completions_changed
- yield self.buffer.on_suggestion_set
-
-
-class SearchBufferControl(BufferControl):
- """
- :class:`.BufferControl` which is used for searching another
- :class:`.BufferControl`.
-
- :param ignore_case: Search case insensitive.
- """
-
- def __init__(
- self,
- buffer: Optional[Buffer] = None,
- input_processors: Optional[List[Processor]] = None,
- lexer: Optional[Lexer] = None,
- focus_on_click: FilterOrBool = False,
- key_bindings: Optional["KeyBindingsBase"] = None,
- ignore_case: FilterOrBool = False,
- ):
-
- super().__init__(
- buffer=buffer,
- input_processors=input_processors,
- lexer=lexer,
- focus_on_click=focus_on_click,
- key_bindings=key_bindings,
- )
-
- # If this BufferControl is used as a search field for one or more other
- # BufferControls, then represents the search state.
- self.searcher_search_state = SearchState(ignore_case=ignore_case)
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dimension.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dimension.py
deleted file mode 100644
index 04c21637cbd..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dimension.py
+++ /dev/null
@@ -1,217 +0,0 @@
-"""
-Layout dimensions are used to give the minimum, maximum and preferred
-dimensions for containers and controls.
-"""
-from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union
-
-__all__ = [
- "Dimension",
- "D",
- "sum_layout_dimensions",
- "max_layout_dimensions",
- "AnyDimension",
- "to_dimension",
- "is_dimension",
-]
-
-if TYPE_CHECKING:
- from typing_extensions import TypeGuard
-
-
-class Dimension:
- """
- 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 with 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: Optional[int] = None,
- max: Optional[int] = None,
- weight: Optional[int] = None,
- preferred: Optional[int] = None,
- ) -> None:
- if weight is not None:
- assert weight >= 0 # Also cannot be a float.
-
- assert min is None or min >= 0
- assert max is None or max >= 0
- assert preferred is None or preferred >= 0
-
- self.min_specified = min is not None
- self.max_specified = max is not None
- self.preferred_specified = preferred is not None
- self.weight_specified = weight is not None
-
- if min is None:
- min = 0 # Smallest possible value.
- if max is None: # 0-values are allowed, so use "is None"
- max = 1000**10 # Something huge.
- if preferred is None:
- preferred = min
- if weight is None:
- weight = 1
-
- self.min = min
- self.max = max
- self.preferred = preferred
- self.weight = weight
-
- # Don't allow situations where max < min. (This would be a bug.)
- if max < min:
- raise ValueError("Invalid Dimension: max < min.")
-
- # Make sure that the 'preferred' size is always in the min..max range.
- if self.preferred < self.min:
- self.preferred = self.min
-
- if self.preferred > self.max:
- self.preferred = self.max
-
- @classmethod
- def exact(cls, amount: int) -> "Dimension":
- """
- Return a :class:`.Dimension` with an exact size. (min, max and
- preferred set to ``amount``).
- """
- return cls(min=amount, max=amount, preferred=amount)
-
- @classmethod
- def zero(cls) -> "Dimension":
- """
- Create a dimension that represents a zero size. (Used for 'invisible'
- controls.)
- """
- return cls.exact(amount=0)
-
- def is_zero(self) -> bool:
- "True if this `Dimension` represents a zero size."
- return self.preferred == 0 or self.max == 0
-
- def __repr__(self) -> str:
- fields = []
- if self.min_specified:
- fields.append("min=%r" % self.min)
- if self.max_specified:
- fields.append("max=%r" % self.max)
- if self.preferred_specified:
- fields.append("preferred=%r" % self.preferred)
- if self.weight_specified:
- fields.append("weight=%r" % self.weight)
-
- return "Dimension(%s)" % ", ".join(fields)
-
-
-def sum_layout_dimensions(dimensions: List[Dimension]) -> Dimension:
- """
- Sum a list of :class:`.Dimension` instances.
- """
- min = sum(d.min for d in dimensions)
- max = sum(d.max for d in dimensions)
- preferred = sum(d.preferred for d in dimensions)
-
- return Dimension(min=min, max=max, preferred=preferred)
-
-
-def max_layout_dimensions(dimensions: List[Dimension]) -> Dimension:
- """
- Take the maximum of a list of :class:`.Dimension` instances.
- Used when we have a HSplit/VSplit, and we want to get the best width/height.)
- """
- if not len(dimensions):
- return Dimension.zero()
-
- # If all dimensions are size zero. Return zero.
- # (This is important for HSplit/VSplit, to report the right values to their
- # parent when all children are invisible.)
- if all(d.is_zero() for d in dimensions):
- return dimensions[0]
-
- # Ignore empty dimensions. (They should not reduce the size of others.)
- dimensions = [d for d in dimensions if not d.is_zero()]
-
- if dimensions:
- # Take the highest minimum dimension.
- min_ = max(d.min for d in dimensions)
-
- # For the maximum, we would prefer not to go larger than then smallest
- # 'max' value, unless other dimensions have a bigger preferred value.
- # This seems to work best:
- # - We don't want that a widget with a small height in a VSplit would
- # shrink other widgets in the split.
- # If it doesn't work well enough, then it's up to the UI designer to
- # explicitly pass dimensions.
- max_ = min(d.max for d in dimensions)
- max_ = max(max_, max(d.preferred for d in dimensions))
-
- # Make sure that min>=max. In some scenarios, when certain min..max
- # ranges don't have any overlap, we can end up in such an impossible
- # situation. In that case, give priority to the max value.
- # E.g. taking (1..5) and (8..9) would return (8..5). Instead take (8..8).
- if min_ > max_:
- max_ = min_
-
- preferred = max(d.preferred for d in dimensions)
-
- return Dimension(min=min_, max=max_, preferred=preferred)
- else:
- return Dimension()
-
-
-# Anything that can be converted to a dimension.
-AnyDimension = Union[
- None, # None is a valid dimension that will fit anything.
- int,
- Dimension,
- # Callable[[], 'AnyDimension'] # Recursive definition not supported by mypy.
- Callable[[], Any],
-]
-
-
-def to_dimension(value: AnyDimension) -> Dimension:
- """
- Turn the given object into a `Dimension` object.
- """
- if value is None:
- return Dimension()
- if isinstance(value, int):
- return Dimension.exact(value)
- if isinstance(value, Dimension):
- return value
- if callable(value):
- return to_dimension(value())
-
- raise ValueError("Not an integer or Dimension object.")
-
-
-def is_dimension(value: object) -> "TypeGuard[AnyDimension]":
- """
- Test whether the given value could be a valid dimension.
- (For usage in an assertion. It's not guaranteed in case of a callable.)
- """
- if value is None:
- return True
- if callable(value):
- return True # Assume it's a callable that doesn't take arguments.
- if isinstance(value, (int, Dimension)):
- return True
- return False
-
-
-# Common alias.
-D = Dimension
-
-# For backward-compatibility.
-LayoutDimension = Dimension
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dummy.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dummy.py
deleted file mode 100644
index dcd54e9fc9f..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/dummy.py
+++ /dev/null
@@ -1,37 +0,0 @@
-"""
-Dummy layout. Used when somebody creates an `Application` without specifying a
-`Layout`.
-"""
-from prompt_toolkit.formatted_text import HTML
-from prompt_toolkit.key_binding import KeyBindings
-from prompt_toolkit.key_binding.key_processor import KeyPressEvent
-
-from .containers import Window
-from .controls import FormattedTextControl
-from .dimension import D
-from .layout import Layout
-
-__all__ = [
- "create_dummy_layout",
-]
-
-E = KeyPressEvent
-
-
-def create_dummy_layout() -> Layout:
- """
- Create a dummy layout for use in an 'Application' that doesn't have a
- layout specified. When ENTER is pressed, the application quits.
- """
- kb = KeyBindings()
-
- @kb.add("enter")
- def enter(event: E) -> None:
- event.app.exit()
-
- control = FormattedTextControl(
- HTML("No layout specified. Press <reverse>ENTER</reverse> to quit."),
- key_bindings=kb,
- )
- window = Window(content=control, height=D(min=1))
- return Layout(container=window, focused_element=window)
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/layout.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/layout.py
deleted file mode 100644
index 62a3184ee22..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/layout.py
+++ /dev/null
@@ -1,411 +0,0 @@
-"""
-Wrapper for the layout.
-"""
-from typing import Dict, Generator, Iterable, List, Optional, Union
-
-from prompt_toolkit.buffer import Buffer
-
-from .containers import (
- AnyContainer,
- ConditionalContainer,
- Container,
- Window,
- to_container,
-)
-from .controls import BufferControl, SearchBufferControl, UIControl
-
-__all__ = [
- "Layout",
- "InvalidLayoutError",
- "walk",
-]
-
-FocusableElement = Union[str, Buffer, UIControl, AnyContainer]
-
-
-class Layout:
- """
- The layout for a prompt_toolkit
- :class:`~prompt_toolkit.application.Application`.
- This also keeps track of which user control is focused.
-
- :param container: The "root" container for the layout.
- :param focused_element: element to be focused initially. (Can be anything
- the `focus` function accepts.)
- """
-
- def __init__(
- self,
- container: AnyContainer,
- focused_element: Optional[FocusableElement] = None,
- ) -> None:
-
- self.container = to_container(container)
- self._stack: List[Window] = []
-
- # Map search BufferControl back to the original BufferControl.
- # This is used to keep track of when exactly we are searching, and for
- # applying the search.
- # When a link exists in this dictionary, that means the search is
- # currently active.
- # Map: search_buffer_control -> original buffer control.
- self.search_links: Dict[SearchBufferControl, BufferControl] = {}
-
- # Mapping that maps the children in the layout to their parent.
- # This relationship is calculated dynamically, each time when the UI
- # is rendered. (UI elements have only references to their children.)
- self._child_to_parent: Dict[Container, Container] = {}
-
- if focused_element is None:
- try:
- self._stack.append(next(self.find_all_windows()))
- except StopIteration as e:
- raise InvalidLayoutError(
- "Invalid layout. The layout does not contain any Window object."
- ) from e
- else:
- self.focus(focused_element)
-
- # List of visible windows.
- self.visible_windows: List[Window] = [] # List of `Window` objects.
-
- def __repr__(self) -> str:
- return f"Layout({self.container!r}, current_window={self.current_window!r})"
-
- def find_all_windows(self) -> Generator[Window, None, None]:
- """
- Find all the :class:`.UIControl` objects in this layout.
- """
- for item in self.walk():
- if isinstance(item, Window):
- yield item
-
- def find_all_controls(self) -> Iterable[UIControl]:
- for container in self.find_all_windows():
- yield container.content
-
- def focus(self, value: FocusableElement) -> None:
- """
- Focus the given UI element.
-
- `value` can be either:
-
- - a :class:`.UIControl`
- - a :class:`.Buffer` instance or the name of a :class:`.Buffer`
- - a :class:`.Window`
- - Any container object. In this case we will focus the :class:`.Window`
- from this container that was focused most recent, or the very first
- focusable :class:`.Window` of the container.
- """
- # BufferControl by buffer name.
- if isinstance(value, str):
- for control in self.find_all_controls():
- if isinstance(control, BufferControl) and control.buffer.name == value:
- self.focus(control)
- return
- raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.")
-
- # BufferControl by buffer object.
- elif isinstance(value, Buffer):
- for control in self.find_all_controls():
- if isinstance(control, BufferControl) and control.buffer == value:
- self.focus(control)
- return
- raise ValueError(f"Couldn't find Buffer in the current layout: {value!r}.")
-
- # Focus UIControl.
- elif isinstance(value, UIControl):
- if value not in self.find_all_controls():
- raise ValueError(
- "Invalid value. Container does not appear in the layout."
- )
- if not value.is_focusable():
- raise ValueError("Invalid value. UIControl is not focusable.")
-
- self.current_control = value
-
- # Otherwise, expecting any Container object.
- else:
- value = to_container(value)
-
- if isinstance(value, Window):
- # This is a `Window`: focus that.
- if value not in self.find_all_windows():
- raise ValueError(
- "Invalid value. Window does not appear in the layout: %r"
- % (value,)
- )
-
- self.current_window = value
- else:
- # Focus a window in this container.
- # If we have many windows as part of this container, and some
- # of them have been focused before, take the last focused
- # item. (This is very useful when the UI is composed of more
- # complex sub components.)
- windows = []
- for c in walk(value, skip_hidden=True):
- if isinstance(c, Window) and c.content.is_focusable():
- windows.append(c)
-
- # Take the first one that was focused before.
- for w in reversed(self._stack):
- if w in windows:
- self.current_window = w
- return
-
- # None was focused before: take the very first focusable window.
- if windows:
- self.current_window = windows[0]
- return
-
- raise ValueError(
- f"Invalid value. Container cannot be focused: {value!r}"
- )
-
- def has_focus(self, value: FocusableElement) -> bool:
- """
- Check whether the given control has the focus.
- :param value: :class:`.UIControl` or :class:`.Window` instance.
- """
- if isinstance(value, str):
- if self.current_buffer is None:
- return False
- return self.current_buffer.name == value
- if isinstance(value, Buffer):
- return self.current_buffer == value
- if isinstance(value, UIControl):
- return self.current_control == value
- else:
- value = to_container(value)
- if isinstance(value, Window):
- return self.current_window == value
- else:
- # Check whether this "container" is focused. This is true if
- # one of the elements inside is focused.
- for element in walk(value):
- if element == self.current_window:
- return True
- return False
-
- @property
- def current_control(self) -> UIControl:
- """
- Get the :class:`.UIControl` to currently has the focus.
- """
- return self._stack[-1].content
-
- @current_control.setter
- def current_control(self, control: UIControl) -> None:
- """
- Set the :class:`.UIControl` to receive the focus.
- """
- for window in self.find_all_windows():
- if window.content == control:
- self.current_window = window
- return
-
- raise ValueError("Control not found in the user interface.")
-
- @property
- def current_window(self) -> Window:
- "Return the :class:`.Window` object that is currently focused."
- return self._stack[-1]
-
- @current_window.setter
- def current_window(self, value: Window) -> None:
- "Set the :class:`.Window` object to be currently focused."
- self._stack.append(value)
-
- @property
- def is_searching(self) -> bool:
- "True if we are searching right now."
- return self.current_control in self.search_links
-
- @property
- def search_target_buffer_control(self) -> Optional[BufferControl]:
- """
- Return the :class:`.BufferControl` in which we are searching or `None`.
- """
- # Not every `UIControl` is a `BufferControl`. This only applies to
- # `BufferControl`.
- control = self.current_control
-
- if isinstance(control, SearchBufferControl):
- return self.search_links.get(control)
- else:
- return None
-
- def get_focusable_windows(self) -> Iterable[Window]:
- """
- Return all the :class:`.Window` objects which are focusable (in the
- 'modal' area).
- """
- for w in self.walk_through_modal_area():
- if isinstance(w, Window) and w.content.is_focusable():
- yield w
-
- def get_visible_focusable_windows(self) -> List[Window]:
- """
- Return a list of :class:`.Window` objects that are focusable.
- """
- # focusable windows are windows that are visible, but also part of the
- # modal container. Make sure to keep the ordering.
- visible_windows = self.visible_windows
- return [w for w in self.get_focusable_windows() if w in visible_windows]
-
- @property
- def current_buffer(self) -> Optional[Buffer]:
- """
- The currently focused :class:`~.Buffer` or `None`.
- """
- ui_control = self.current_control
- if isinstance(ui_control, BufferControl):
- return ui_control.buffer
- return None
-
- def get_buffer_by_name(self, buffer_name: str) -> Optional[Buffer]:
- """
- Look in the layout for a buffer with the given name.
- Return `None` when nothing was found.
- """
- for w in self.walk():
- if isinstance(w, Window) and isinstance(w.content, BufferControl):
- if w.content.buffer.name == buffer_name:
- return w.content.buffer
- return None
-
- @property
- def buffer_has_focus(self) -> bool:
- """
- Return `True` if the currently focused control is a
- :class:`.BufferControl`. (For instance, used to determine whether the
- default key bindings should be active or not.)
- """
- ui_control = self.current_control
- return isinstance(ui_control, BufferControl)
-
- @property
- def previous_control(self) -> UIControl:
- """
- Get the :class:`.UIControl` to previously had the focus.
- """
- try:
- return self._stack[-2].content
- except IndexError:
- return self._stack[-1].content
-
- def focus_last(self) -> None:
- """
- Give the focus to the last focused control.
- """
- if len(self._stack) > 1:
- self._stack = self._stack[:-1]
-
- def focus_next(self) -> None:
- """
- Focus the next visible/focusable Window.
- """
- windows = self.get_visible_focusable_windows()
-
- if len(windows) > 0:
- try:
- index = windows.index(self.current_window)
- except ValueError:
- index = 0
- else:
- index = (index + 1) % len(windows)
-
- self.focus(windows[index])
-
- def focus_previous(self) -> None:
- """
- Focus the previous visible/focusable Window.
- """
- windows = self.get_visible_focusable_windows()
-
- if len(windows) > 0:
- try:
- index = windows.index(self.current_window)
- except ValueError:
- index = 0
- else:
- index = (index - 1) % len(windows)
-
- self.focus(windows[index])
-
- def walk(self) -> Iterable[Container]:
- """
- Walk through all the layout nodes (and their children) and yield them.
- """
- yield from walk(self.container)
-
- def walk_through_modal_area(self) -> Iterable[Container]:
- """
- Walk through all the containers which are in the current 'modal' part
- of the layout.
- """
- # Go up in the tree, and find the root. (it will be a part of the
- # layout, if the focus is in a modal part.)
- root: Container = self.current_window
- while not root.is_modal() and root in self._child_to_parent:
- root = self._child_to_parent[root]
-
- yield from walk(root)
-
- def update_parents_relations(self) -> None:
- """
- Update child->parent relationships mapping.
- """
- parents = {}
-
- def walk(e: Container) -> None:
- for c in e.get_children():
- parents[c] = e
- walk(c)
-
- walk(self.container)
-
- self._child_to_parent = parents
-
- def reset(self) -> None:
- # Remove all search links when the UI starts.
- # (Important, for instance when control-c is been pressed while
- # searching. The prompt cancels, but next `run()` call the search
- # links are still there.)
- self.search_links.clear()
-
- self.container.reset()
-
- def get_parent(self, container: Container) -> Optional[Container]:
- """
- Return the parent container for the given container, or ``None``, if it
- wasn't found.
- """
- try:
- return self._child_to_parent[container]
- except KeyError:
- return None
-
-
-class InvalidLayoutError(Exception):
- pass
-
-
-def walk(container: Container, skip_hidden: bool = False) -> Iterable[Container]:
- """
- Walk through layout, starting at this container.
- """
- # When `skip_hidden` is set, don't go into disabled ConditionalContainer containers.
- if (
- skip_hidden
- and isinstance(container, ConditionalContainer)
- and not container.filter()
- ):
- return
-
- yield container
-
- for c in container.get_children():
- # yield from walk(c)
- yield from walk(c, skip_hidden=skip_hidden)
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/margins.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/margins.py
deleted file mode 100644
index 7c46819c24f..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/margins.py
+++ /dev/null
@@ -1,305 +0,0 @@
-"""
-Margin implementations for a :class:`~prompt_toolkit.layout.containers.Window`.
-"""
-from abc import ABCMeta, abstractmethod
-from typing import TYPE_CHECKING, Callable, Optional
-
-from prompt_toolkit.filters import FilterOrBool, to_filter
-from prompt_toolkit.formatted_text import (
- StyleAndTextTuples,
- fragment_list_to_text,
- to_formatted_text,
-)
-from prompt_toolkit.utils import get_cwidth
-
-from .controls import UIContent
-
-if TYPE_CHECKING:
- from .containers import WindowRenderInfo
-
-__all__ = [
- "Margin",
- "NumberedMargin",
- "ScrollbarMargin",
- "ConditionalMargin",
- "PromptMargin",
-]
-
-
-class Margin(metaclass=ABCMeta):
- """
- Base interface for a margin.
- """
-
- @abstractmethod
- def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
- """
- Return the width that this margin is going to consume.
-
- :param get_ui_content: Callable that asks the user control to create
- a :class:`.UIContent` instance. This can be used for instance to
- obtain the number of lines.
- """
- return 0
-
- @abstractmethod
- def create_margin(
- self, window_render_info: "WindowRenderInfo", width: int, height: int
- ) -> StyleAndTextTuples:
- """
- Creates a margin.
- This should return a list of (style_str, text) tuples.
-
- :param window_render_info:
- :class:`~prompt_toolkit.layout.containers.WindowRenderInfo`
- instance, generated after rendering and copying the visible part of
- the :class:`~prompt_toolkit.layout.controls.UIControl` into the
- :class:`~prompt_toolkit.layout.containers.Window`.
- :param width: The width that's available for this margin. (As reported
- by :meth:`.get_width`.)
- :param height: The height that's available for this margin. (The height
- of the :class:`~prompt_toolkit.layout.containers.Window`.)
- """
- return []
-
-
-class NumberedMargin(Margin):
- """
- Margin that displays the line numbers.
-
- :param relative: Number relative to the cursor position. Similar to the Vi
- 'relativenumber' option.
- :param display_tildes: Display tildes after the end of the document, just
- like Vi does.
- """
-
- def __init__(
- self, relative: FilterOrBool = False, display_tildes: FilterOrBool = False
- ) -> None:
-
- self.relative = to_filter(relative)
- self.display_tildes = to_filter(display_tildes)
-
- def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
- line_count = get_ui_content().line_count
- return max(3, len("%s" % line_count) + 1)
-
- def create_margin(
- self, window_render_info: "WindowRenderInfo", width: int, height: int
- ) -> StyleAndTextTuples:
- relative = self.relative()
-
- style = "class:line-number"
- style_current = "class:line-number.current"
-
- # Get current line number.
- current_lineno = window_render_info.ui_content.cursor_position.y
-
- # Construct margin.
- result: StyleAndTextTuples = []
- last_lineno = None
-
- for y, lineno in enumerate(window_render_info.displayed_lines):
- # Only display line number if this line is not a continuation of the previous line.
- if lineno != last_lineno:
- if lineno is None:
- pass
- elif lineno == current_lineno:
- # Current line.
- if relative:
- # Left align current number in relative mode.
- result.append((style_current, "%i" % (lineno + 1)))
- else:
- result.append(
- (style_current, ("%i " % (lineno + 1)).rjust(width))
- )
- else:
- # Other lines.
- if relative:
- lineno = abs(lineno - current_lineno) - 1
-
- result.append((style, ("%i " % (lineno + 1)).rjust(width)))
-
- last_lineno = lineno
- result.append(("", "\n"))
-
- # Fill with tildes.
- if self.display_tildes():
- while y < window_render_info.window_height:
- result.append(("class:tilde", "~\n"))
- y += 1
-
- return result
-
-
-class ConditionalMargin(Margin):
- """
- Wrapper around other :class:`.Margin` classes to show/hide them.
- """
-
- def __init__(self, margin: Margin, filter: FilterOrBool) -> None:
- self.margin = margin
- self.filter = to_filter(filter)
-
- def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
- if self.filter():
- return self.margin.get_width(get_ui_content)
- else:
- return 0
-
- def create_margin(
- self, window_render_info: "WindowRenderInfo", width: int, height: int
- ) -> StyleAndTextTuples:
- if width and self.filter():
- return self.margin.create_margin(window_render_info, width, height)
- else:
- return []
-
-
-class ScrollbarMargin(Margin):
- """
- Margin displaying a scrollbar.
-
- :param display_arrows: Display scroll up/down arrows.
- """
-
- def __init__(
- self,
- display_arrows: FilterOrBool = False,
- up_arrow_symbol: str = "^",
- down_arrow_symbol: str = "v",
- ) -> None:
-
- self.display_arrows = to_filter(display_arrows)
- self.up_arrow_symbol = up_arrow_symbol
- self.down_arrow_symbol = down_arrow_symbol
-
- def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
- return 1
-
- def create_margin(
- self, window_render_info: "WindowRenderInfo", width: int, height: int
- ) -> StyleAndTextTuples:
- content_height = window_render_info.content_height
- window_height = window_render_info.window_height
- display_arrows = self.display_arrows()
-
- if display_arrows:
- window_height -= 2
-
- try:
- fraction_visible = len(window_render_info.displayed_lines) / float(
- content_height
- )
- fraction_above = window_render_info.vertical_scroll / float(content_height)
-
- scrollbar_height = int(
- min(window_height, max(1, window_height * fraction_visible))
- )
- scrollbar_top = int(window_height * fraction_above)
- except ZeroDivisionError:
- return []
- else:
-
- def is_scroll_button(row: int) -> bool:
- "True if we should display a button on this row."
- return scrollbar_top <= row <= scrollbar_top + scrollbar_height
-
- # Up arrow.
- result: StyleAndTextTuples = []
- if display_arrows:
- result.extend(
- [
- ("class:scrollbar.arrow", self.up_arrow_symbol),
- ("class:scrollbar", "\n"),
- ]
- )
-
- # Scrollbar body.
- scrollbar_background = "class:scrollbar.background"
- scrollbar_background_start = "class:scrollbar.background,scrollbar.start"
- scrollbar_button = "class:scrollbar.button"
- scrollbar_button_end = "class:scrollbar.button,scrollbar.end"
-
- for i in range(window_height):
- if is_scroll_button(i):
- if not is_scroll_button(i + 1):
- # Give the last cell a different style, because we
- # want to underline this.
- result.append((scrollbar_button_end, " "))
- else:
- result.append((scrollbar_button, " "))
- else:
- if is_scroll_button(i + 1):
- result.append((scrollbar_background_start, " "))
- else:
- result.append((scrollbar_background, " "))
- result.append(("", "\n"))
-
- # Down arrow
- if display_arrows:
- result.append(("class:scrollbar.arrow", self.down_arrow_symbol))
-
- return result
-
-
-class PromptMargin(Margin):
- """
- [Deprecated]
-
- Create margin that displays a prompt.
- This can display one prompt at the first line, and a continuation prompt
- (e.g, just dots) on all the following lines.
-
- This `PromptMargin` implementation has been largely superseded in favor of
- the `get_line_prefix` attribute of `Window`. The reason is that a margin is
- always a fixed width, while `get_line_prefix` can return a variable width
- prefix in front of every line, making it more powerful, especially for line
- continuations.
-
- :param get_prompt: Callable returns formatted text or a list of
- `(style_str, type)` tuples to be shown as the prompt at the first line.
- :param get_continuation: Callable that takes three inputs. The width (int),
- line_number (int), and is_soft_wrap (bool). It should return formatted
- text or a list of `(style_str, type)` tuples for the next lines of the
- input.
- """
-
- def __init__(
- self,
- get_prompt: Callable[[], StyleAndTextTuples],
- get_continuation: Optional[
- Callable[[int, int, bool], StyleAndTextTuples]
- ] = None,
- ) -> None:
-
- self.get_prompt = get_prompt
- self.get_continuation = get_continuation
-
- def get_width(self, get_ui_content: Callable[[], UIContent]) -> int:
- "Width to report to the `Window`."
- # Take the width from the first line.
- text = fragment_list_to_text(self.get_prompt())
- return get_cwidth(text)
-
- def create_margin(
- self, window_render_info: "WindowRenderInfo", width: int, height: int
- ) -> StyleAndTextTuples:
- get_continuation = self.get_continuation
- result: StyleAndTextTuples = []
-
- # First line.
- result.extend(to_formatted_text(self.get_prompt()))
-
- # Next lines.
- if get_continuation:
- last_y = None
-
- for y in window_render_info.displayed_lines[1:]:
- result.append(("", "\n"))
- result.extend(
- to_formatted_text(get_continuation(width, y, y == last_y))
- )
- last_y = y
-
- return result
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py
deleted file mode 100644
index 24d6e46af05..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/menus.py
+++ /dev/null
@@ -1,723 +0,0 @@
-import math
-from itertools import zip_longest
-from typing import (
- TYPE_CHECKING,
- Callable,
- Dict,
- Iterable,
- List,
- Optional,
- Sequence,
- Tuple,
- TypeVar,
- Union,
- cast,
-)
-
-from prompt_toolkit.application.current import get_app
-from prompt_toolkit.buffer import CompletionState
-from prompt_toolkit.completion import Completion
-from prompt_toolkit.data_structures import Point
-from prompt_toolkit.filters import (
- Condition,
- FilterOrBool,
- has_completions,
- is_done,
- to_filter,
-)
-from prompt_toolkit.formatted_text import (
- StyleAndTextTuples,
- fragment_list_width,
- to_formatted_text,
-)
-from prompt_toolkit.key_binding.key_processor import KeyPressEvent
-from prompt_toolkit.layout.utils import explode_text_fragments
-from prompt_toolkit.mouse_events import MouseEvent, MouseEventType
-from prompt_toolkit.utils import get_cwidth
-
-from .containers import ConditionalContainer, HSplit, ScrollOffsets, Window
-from .controls import GetLinePrefixCallable, UIContent, UIControl
-from .dimension import Dimension
-from .margins import ScrollbarMargin
-
-if TYPE_CHECKING:
- from prompt_toolkit.key_binding.key_bindings import (
- KeyBindings,
- NotImplementedOrNone,
- )
-
-
-__all__ = [
- "CompletionsMenu",
- "MultiColumnCompletionsMenu",
-]
-
-E = KeyPressEvent
-
-
-class CompletionsMenuControl(UIControl):
- """
- Helper for drawing the complete menu to the screen.
-
- :param scroll_offset: Number (integer) representing the preferred amount of
- completions to be displayed before and after the current one. When this
- is a very high number, the current completion will be shown in the
- middle most of the time.
- """
-
- # Preferred minimum size of the menu control.
- # The CompletionsMenu class defines a width of 8, and there is a scrollbar
- # of 1.)
- MIN_WIDTH = 7
-
- def has_focus(self) -> bool:
- return False
-
- def preferred_width(self, max_available_width: int) -> Optional[int]:
- complete_state = get_app().current_buffer.complete_state
- if complete_state:
- menu_width = self._get_menu_width(500, complete_state)
- menu_meta_width = self._get_menu_meta_width(500, complete_state)
-
- return menu_width + menu_meta_width
- else:
- return 0
-
- def preferred_height(
- self,
- width: int,
- max_available_height: int,
- wrap_lines: bool,
- get_line_prefix: Optional[GetLinePrefixCallable],
- ) -> Optional[int]:
-
- complete_state = get_app().current_buffer.complete_state
- if complete_state:
- return len(complete_state.completions)
- else:
- return 0
-
- def create_content(self, width: int, height: int) -> UIContent:
- """
- Create a UIContent object for this control.
- """
- complete_state = get_app().current_buffer.complete_state
- if complete_state:
- completions = complete_state.completions
- index = complete_state.complete_index # Can be None!
-
- # Calculate width of completions menu.
- menu_width = self._get_menu_width(width, complete_state)
- menu_meta_width = self._get_menu_meta_width(
- width - menu_width, complete_state
- )
- show_meta = self._show_meta(complete_state)
-
- def get_line(i: int) -> StyleAndTextTuples:
- c = completions[i]
- is_current_completion = i == index
- result = _get_menu_item_fragments(
- c, is_current_completion, menu_width, space_after=True
- )
-
- if show_meta:
- result += self._get_menu_item_meta_fragments(
- c, is_current_completion, menu_meta_width
- )
- return result
-
- return UIContent(
- get_line=get_line,
- cursor_position=Point(x=0, y=index or 0),
- line_count=len(completions),
- )
-
- return UIContent()
-
- def _show_meta(self, complete_state: CompletionState) -> bool:
- """
- Return ``True`` if we need to show a column with meta information.
- """
- return any(c.display_meta_text for c in complete_state.completions)
-
- def _get_menu_width(self, max_width: int, complete_state: CompletionState) -> int:
- """
- Return the width of the main column.
- """
- return min(
- max_width,
- max(
- self.MIN_WIDTH,
- max(get_cwidth(c.display_text) for c in complete_state.completions) + 2,
- ),
- )
-
- def _get_menu_meta_width(
- self, max_width: int, complete_state: CompletionState
- ) -> int:
- """
- Return the width of the meta column.
- """
-
- def meta_width(completion: Completion) -> int:
- return get_cwidth(completion.display_meta_text)
-
- if self._show_meta(complete_state):
- return min(
- max_width, max(meta_width(c) for c in complete_state.completions) + 2
- )
- else:
- return 0
-
- def _get_menu_item_meta_fragments(
- self, completion: Completion, is_current_completion: bool, width: int
- ) -> StyleAndTextTuples:
-
- if is_current_completion:
- style_str = "class:completion-menu.meta.completion.current"
- else:
- style_str = "class:completion-menu.meta.completion"
-
- text, tw = _trim_formatted_text(completion.display_meta, width - 2)
- padding = " " * (width - 1 - tw)
-
- return to_formatted_text(
- cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)],
- style=style_str,
- )
-
- def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- Handle mouse events: clicking and scrolling.
- """
- b = get_app().current_buffer
-
- if mouse_event.event_type == MouseEventType.MOUSE_UP:
- # Select completion.
- b.go_to_completion(mouse_event.position.y)
- b.complete_state = None
-
- elif mouse_event.event_type == MouseEventType.SCROLL_DOWN:
- # Scroll up.
- b.complete_next(count=3, disable_wrap_around=True)
-
- elif mouse_event.event_type == MouseEventType.SCROLL_UP:
- # Scroll down.
- b.complete_previous(count=3, disable_wrap_around=True)
-
- return None
-
-
-def _get_menu_item_fragments(
- completion: Completion,
- is_current_completion: bool,
- width: int,
- space_after: bool = False,
-) -> StyleAndTextTuples:
- """
- Get the style/text tuples for a menu item, styled and trimmed to the given
- width.
- """
- if is_current_completion:
- style_str = "class:completion-menu.completion.current {} {}".format(
- completion.style,
- completion.selected_style,
- )
- else:
- style_str = "class:completion-menu.completion " + completion.style
-
- text, tw = _trim_formatted_text(
- completion.display, (width - 2 if space_after else width - 1)
- )
-
- padding = " " * (width - 1 - tw)
-
- return to_formatted_text(
- cast(StyleAndTextTuples, []) + [("", " ")] + text + [("", padding)],
- style=style_str,
- )
-
-
-def _trim_formatted_text(
- formatted_text: StyleAndTextTuples, max_width: int
-) -> Tuple[StyleAndTextTuples, int]:
- """
- Trim the text to `max_width`, append dots when the text is too long.
- Returns (text, width) tuple.
- """
- width = fragment_list_width(formatted_text)
-
- # When the text is too wide, trim it.
- if width > max_width:
- result = [] # Text fragments.
- remaining_width = max_width - 3
-
- for style_and_ch in explode_text_fragments(formatted_text):
- ch_width = get_cwidth(style_and_ch[1])
-
- if ch_width <= remaining_width:
- result.append(style_and_ch)
- remaining_width -= ch_width
- else:
- break
-
- result.append(("", "..."))
-
- return result, max_width - remaining_width
- else:
- return formatted_text, width
-
-
-class CompletionsMenu(ConditionalContainer):
- # NOTE: We use a pretty big z_index by default. Menus are supposed to be
- # above anything else. We also want to make sure that the content is
- # visible at the point where we draw this menu.
- def __init__(
- self,
- max_height: Optional[int] = None,
- scroll_offset: Union[int, Callable[[], int]] = 0,
- extra_filter: FilterOrBool = True,
- display_arrows: FilterOrBool = False,
- z_index: int = 10**8,
- ) -> None:
-
- extra_filter = to_filter(extra_filter)
- display_arrows = to_filter(display_arrows)
-
- super().__init__(
- content=Window(
- content=CompletionsMenuControl(),
- width=Dimension(min=8),
- height=Dimension(min=1, max=max_height),
- scroll_offsets=ScrollOffsets(top=scroll_offset, bottom=scroll_offset),
- right_margins=[ScrollbarMargin(display_arrows=display_arrows)],
- dont_extend_width=True,
- style="class:completion-menu",
- z_index=z_index,
- ),
- # Show when there are completions but not at the point we are
- # returning the input.
- filter=has_completions & ~is_done & extra_filter,
- )
-
-
-class MultiColumnCompletionMenuControl(UIControl):
- """
- Completion menu that displays all the completions in several columns.
- When there are more completions than space for them to be displayed, an
- arrow is shown on the left or right side.
-
- `min_rows` indicates how many rows will be available in any possible case.
- When this is larger than one, it will try to use less columns and more
- rows until this value is reached.
- Be careful passing in a too big value, if less than the given amount of
- rows are available, more columns would have been required, but
- `preferred_width` doesn't know about that and reports a too small value.
- This results in less completions displayed and additional scrolling.
- (It's a limitation of how the layout engine currently works: first the
- widths are calculated, then the heights.)
-
- :param suggested_max_column_width: The suggested max width of a column.
- The column can still be bigger than this, but if there is place for two
- columns of this width, we will display two columns. This to avoid that
- if there is one very wide completion, that it doesn't significantly
- reduce the amount of columns.
- """
-
- _required_margin = 3 # One extra padding on the right + space for arrows.
-
- def __init__(self, min_rows: int = 3, suggested_max_column_width: int = 30) -> None:
- assert min_rows >= 1
-
- self.min_rows = min_rows
- self.suggested_max_column_width = suggested_max_column_width
- self.scroll = 0
-
- # Info of last rendering.
- self._rendered_rows = 0
- self._rendered_columns = 0
- self._total_columns = 0
- self._render_pos_to_completion: Dict[Tuple[int, int], Completion] = {}
- self._render_left_arrow = False
- self._render_right_arrow = False
- self._render_width = 0
-
- def reset(self) -> None:
- self.scroll = 0
-
- def has_focus(self) -> bool:
- return False
-
- def preferred_width(self, max_available_width: int) -> Optional[int]:
- """
- Preferred width: prefer to use at least min_rows, but otherwise as much
- as possible horizontally.
- """
- complete_state = get_app().current_buffer.complete_state
- if complete_state is None:
- return 0
-
- column_width = self._get_column_width(complete_state)
- result = int(
- column_width
- * math.ceil(len(complete_state.completions) / float(self.min_rows))
- )
-
- # When the desired width is still more than the maximum available,
- # reduce by removing columns until we are less than the available
- # width.
- while (
- result > column_width
- and result > max_available_width - self._required_margin
- ):
- result -= column_width
- return result + self._required_margin
-
- def preferred_height(
- self,
- width: int,
- max_available_height: int,
- wrap_lines: bool,
- get_line_prefix: Optional[GetLinePrefixCallable],
- ) -> Optional[int]:
- """
- Preferred height: as much as needed in order to display all the completions.
- """
- complete_state = get_app().current_buffer.complete_state
- if complete_state is None:
- return 0
-
- column_width = self._get_column_width(complete_state)
- column_count = max(1, (width - self._required_margin) // column_width)
-
- return int(math.ceil(len(complete_state.completions) / float(column_count)))
-
- def create_content(self, width: int, height: int) -> UIContent:
- """
- Create a UIContent object for this menu.
- """
- complete_state = get_app().current_buffer.complete_state
- if complete_state is None:
- return UIContent()
-
- column_width = self._get_column_width(complete_state)
- self._render_pos_to_completion = {}
-
- _T = TypeVar("_T")
-
- def grouper(
- n: int, iterable: Iterable[_T], fillvalue: Optional[_T] = None
- ) -> Iterable[Sequence[Optional[_T]]]:
- "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
- args = [iter(iterable)] * n
- return zip_longest(fillvalue=fillvalue, *args)
-
- def is_current_completion(completion: Completion) -> bool:
- "Returns True when this completion is the currently selected one."
- return (
- complete_state is not None
- and 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
-
- # There should be at least one column, but it cannot be wider than
- # the available width.
- column_width = min(width - HORIZONTAL_MARGIN_REQUIRED, column_width)
-
- # However, when the columns tend to be very wide, because there are
- # some very wide entries, shrink it anyway.
- if column_width > self.suggested_max_column_width:
- # `column_width` can still be bigger that `suggested_max_column_width`,
- # but if there is place for two columns, we divide by two.
- column_width //= column_width // self.suggested_max_column_width
-
- visible_columns = max(1, (width - self._required_margin) // column_width)
-
- columns_ = list(grouper(height, complete_state.completions))
- rows_ = list(zip(*columns_))
-
- # Make sure the current completion is always visible: update scroll offset.
- selected_column = (complete_state.complete_index or 0) // height
- self.scroll = min(
- selected_column, max(self.scroll, selected_column - visible_columns + 1)
- )
-
- render_left_arrow = self.scroll > 0
- render_right_arrow = self.scroll < len(rows_[0]) - visible_columns
-
- # Write completions to screen.
- fragments_for_line = []
-
- for row_index, row in enumerate(rows_):
- fragments: StyleAndTextTuples = []
- middle_row = row_index == len(rows_) // 2
-
- # Draw left arrow if we have hidden completions on the left.
- if render_left_arrow:
- fragments.append(("class:scrollbar", "<" if middle_row else " "))
- elif render_right_arrow:
- # Reserve one column empty space. (If there is a right
- # arrow right now, there can be a left arrow as well.)
- fragments.append(("", " "))
-
- # Draw row content.
- for column_index, c in enumerate(row[self.scroll :][:visible_columns]):
- if c is not None:
- fragments += _get_menu_item_fragments(
- c, is_current_completion(c), column_width, space_after=False
- )
-
- # Remember render position for mouse click handler.
- for x in range(column_width):
- self._render_pos_to_completion[
- (column_index * column_width + x, row_index)
- ] = c
- else:
- fragments.append(("class:completion", " " * column_width))
-
- # Draw trailing padding for this row.
- # (_get_menu_item_fragments only returns padding on the left.)
- if render_left_arrow or render_right_arrow:
- fragments.append(("class:completion", " "))
-
- # Draw right arrow if we have hidden completions on the right.
- if render_right_arrow:
- fragments.append(("class:scrollbar", ">" if middle_row else " "))
- elif render_left_arrow:
- fragments.append(("class:completion", " "))
-
- # Add line.
- fragments_for_line.append(
- to_formatted_text(fragments, style="class:completion-menu")
- )
-
- 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: int) -> StyleAndTextTuples:
- return fragments_for_line[i]
-
- return UIContent(get_line=get_line, line_count=len(rows_))
-
- def _get_column_width(self, complete_state: CompletionState) -> int:
- """
- Return the width of each column.
- """
- return max(get_cwidth(c.display_text) for c in complete_state.completions) + 1
-
- def mouse_handler(self, mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- Handle scroll and click events.
- """
- b = get_app().current_buffer
-
- def scroll_left() -> None:
- b.complete_previous(count=self._rendered_rows, disable_wrap_around=True)
- self.scroll = max(0, self.scroll - 1)
-
- def scroll_right() -> None:
- b.complete_next(count=self._rendered_rows, disable_wrap_around=True)
- self.scroll = min(
- self._total_columns - self._rendered_columns, self.scroll + 1
- )
-
- if mouse_event.event_type == MouseEventType.SCROLL_DOWN:
- scroll_right()
-
- elif mouse_event.event_type == MouseEventType.SCROLL_UP:
- scroll_left()
-
- elif mouse_event.event_type == MouseEventType.MOUSE_UP:
- x = mouse_event.position.x
- y = mouse_event.position.y
-
- # Mouse click on left arrow.
- if x == 0:
- if self._render_left_arrow:
- scroll_left()
-
- # Mouse click on right arrow.
- elif x == self._render_width - 1:
- if self._render_right_arrow:
- scroll_right()
-
- # Mouse click on completion.
- else:
- completion = self._render_pos_to_completion.get((x, y))
- if completion:
- b.apply_completion(completion)
-
- return None
-
- def get_key_bindings(self) -> "KeyBindings":
- """
- Expose key bindings that handle the left/right arrow keys when the menu
- is displayed.
- """
- from prompt_toolkit.key_binding.key_bindings import KeyBindings
-
- kb = KeyBindings()
-
- @Condition
- def filter() -> bool:
- "Only handle key bindings if this menu is visible."
- app = get_app()
- complete_state = app.current_buffer.complete_state
-
- # There need to be completions, and one needs to be selected.
- if complete_state is None or complete_state.complete_index is None:
- return False
-
- # This menu needs to be visible.
- return any(window.content == self for window in app.layout.visible_windows)
-
- def move(right: bool = False) -> None:
- buff = get_app().current_buffer
- complete_state = buff.complete_state
-
- if complete_state is not None and complete_state.complete_index is not None:
- # Calculate new complete index.
- new_index = complete_state.complete_index
- if right:
- new_index += self._rendered_rows
- else:
- new_index -= self._rendered_rows
-
- if 0 <= new_index < len(complete_state.completions):
- buff.go_to_completion(new_index)
-
- # NOTE: the is_global is required because the completion menu will
- # never be focussed.
-
- @kb.add("left", is_global=True, filter=filter)
- def _left(event: E) -> None:
- move()
-
- @kb.add("right", is_global=True, filter=filter)
- def _right(event: E) -> None:
- move(True)
-
- return kb
-
-
-class MultiColumnCompletionsMenu(HSplit):
- """
- Container that displays the completions in several columns.
- When `show_meta` (a :class:`~prompt_toolkit.filters.Filter`) evaluates
- to True, it shows the meta information at the bottom.
- """
-
- def __init__(
- self,
- min_rows: int = 3,
- suggested_max_column_width: int = 30,
- show_meta: FilterOrBool = True,
- extra_filter: FilterOrBool = True,
- z_index: int = 10**8,
- ) -> None:
-
- show_meta = to_filter(show_meta)
- extra_filter = to_filter(extra_filter)
-
- # Display filter: show when there are completions but not at the point
- # we are returning the input.
- full_filter = has_completions & ~is_done & extra_filter
-
- @Condition
- def any_completion_has_meta() -> bool:
- complete_state = get_app().current_buffer.complete_state
- return complete_state is not None and any(
- c.display_meta for c in complete_state.completions
- )
-
- # Create child windows.
- # NOTE: We don't set style='class:completion-menu' to the
- # `MultiColumnCompletionMenuControl`, because this is used in a
- # Float that is made transparent, and the size of the control
- # doesn't always correspond exactly with the size of the
- # generated content.
- completions_window = ConditionalContainer(
- content=Window(
- content=MultiColumnCompletionMenuControl(
- min_rows=min_rows,
- suggested_max_column_width=suggested_max_column_width,
- ),
- width=Dimension(min=8),
- height=Dimension(min=1),
- ),
- filter=full_filter,
- )
-
- meta_window = ConditionalContainer(
- content=Window(content=_SelectedCompletionMetaControl()),
- filter=show_meta & full_filter & any_completion_has_meta,
- )
-
- # Initialise split.
- super().__init__([completions_window, meta_window], z_index=z_index)
-
-
-class _SelectedCompletionMetaControl(UIControl):
- """
- Control that shows the meta information of the selected completion.
- """
-
- def preferred_width(self, max_available_width: int) -> Optional[int]:
- """
- Report the width of the longest meta text as the preferred width of this control.
-
- It could be that we use less width, but this way, we're sure that the
- layout doesn't change when we select another completion (E.g. that
- completions are suddenly shown in more or fewer columns.)
- """
- app = get_app()
- if app.current_buffer.complete_state:
- state = app.current_buffer.complete_state
- return 2 + max(get_cwidth(c.display_meta_text) for c in state.completions)
- else:
- return 0
-
- def preferred_height(
- self,
- width: int,
- max_available_height: int,
- wrap_lines: bool,
- get_line_prefix: Optional[GetLinePrefixCallable],
- ) -> Optional[int]:
- return 1
-
- def create_content(self, width: int, height: int) -> UIContent:
- fragments = self._get_text_fragments()
-
- def get_line(i: int) -> StyleAndTextTuples:
- return fragments
-
- return UIContent(get_line=get_line, line_count=1 if fragments else 0)
-
- def _get_text_fragments(self) -> StyleAndTextTuples:
- style = "class:completion-menu.multi-column-meta"
- state = get_app().current_buffer.complete_state
-
- if (
- state
- and state.current_completion
- and state.current_completion.display_meta_text
- ):
- return to_formatted_text(
- cast(StyleAndTextTuples, [("", " ")])
- + state.current_completion.display_meta
- + [("", " ")],
- style=style,
- )
-
- return []
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/mouse_handlers.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/mouse_handlers.py
deleted file mode 100644
index 256231793a9..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/mouse_handlers.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from collections import defaultdict
-from typing import TYPE_CHECKING, Callable, DefaultDict
-
-from prompt_toolkit.mouse_events import MouseEvent
-
-if TYPE_CHECKING:
- from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone
-
-__all__ = [
- "MouseHandler",
- "MouseHandlers",
-]
-
-
-MouseHandler = Callable[[MouseEvent], "NotImplementedOrNone"]
-
-
-class MouseHandlers:
- """
- Two dimensional raster of callbacks for mouse events.
- """
-
- def __init__(self) -> None:
- def dummy_callback(mouse_event: MouseEvent) -> "NotImplementedOrNone":
- """
- :param mouse_event: `MouseEvent` instance.
- """
- return NotImplemented
-
- # NOTE: Previously, the data structure was a dictionary mapping (x,y)
- # to the handlers. This however would be more inefficient when copying
- # over the mouse handlers of the visible region in the scrollable pane.
-
- # Map y (row) to x (column) to handlers.
- self.mouse_handlers: DefaultDict[
- int, DefaultDict[int, MouseHandler]
- ] = defaultdict(lambda: defaultdict(lambda: dummy_callback))
-
- def set_mouse_handler_for_range(
- self,
- x_min: int,
- x_max: int,
- y_min: int,
- y_max: int,
- handler: Callable[[MouseEvent], "NotImplementedOrNone"],
- ) -> None:
- """
- Set mouse handler for a region.
- """
- for y in range(y_min, y_max):
- row = self.mouse_handlers[y]
-
- for x in range(x_min, x_max):
- row[x] = handler
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/processors.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/processors.py
deleted file mode 100644
index 722658a846b..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/processors.py
+++ /dev/null
@@ -1,1029 +0,0 @@
-"""
-Processors are little transformation blocks that transform the fragments list
-from a buffer before the BufferControl will render it to the screen.
-
-They can insert fragments before or after, or highlight fragments by replacing the
-fragment types.
-"""
-import re
-from abc import ABCMeta, abstractmethod
-from typing import (
- TYPE_CHECKING,
- Callable,
- Hashable,
- List,
- Optional,
- Tuple,
- Type,
- Union,
- cast,
-)
-
-from prompt_toolkit.application.current import get_app
-from prompt_toolkit.cache import SimpleCache
-from prompt_toolkit.document import Document
-from prompt_toolkit.filters import FilterOrBool, to_filter, vi_insert_multiple_mode
-from prompt_toolkit.formatted_text import (
- AnyFormattedText,
- StyleAndTextTuples,
- to_formatted_text,
-)
-from prompt_toolkit.formatted_text.utils import fragment_list_len, fragment_list_to_text
-from prompt_toolkit.search import SearchDirection
-from prompt_toolkit.utils import to_int, to_str
-
-from .utils import explode_text_fragments
-
-if TYPE_CHECKING:
- from .controls import BufferControl, UIContent
-
-__all__ = [
- "Processor",
- "TransformationInput",
- "Transformation",
- "DummyProcessor",
- "HighlightSearchProcessor",
- "HighlightIncrementalSearchProcessor",
- "HighlightSelectionProcessor",
- "PasswordProcessor",
- "HighlightMatchingBracketProcessor",
- "DisplayMultipleCursors",
- "BeforeInput",
- "ShowArg",
- "AfterInput",
- "AppendAutoSuggestion",
- "ConditionalProcessor",
- "ShowLeadingWhiteSpaceProcessor",
- "ShowTrailingWhiteSpaceProcessor",
- "TabsProcessor",
- "ReverseSearchProcessor",
- "DynamicProcessor",
- "merge_processors",
-]
-
-
-class Processor(metaclass=ABCMeta):
- """
- Manipulate the fragments for a given line in a
- :class:`~prompt_toolkit.layout.controls.BufferControl`.
- """
-
- @abstractmethod
- def apply_transformation(
- self, transformation_input: "TransformationInput"
- ) -> "Transformation":
- """
- Apply transformation. Returns a :class:`.Transformation` instance.
-
- :param transformation_input: :class:`.TransformationInput` object.
- """
- return Transformation(transformation_input.fragments)
-
-
-SourceToDisplay = Callable[[int], int]
-DisplayToSource = Callable[[int], int]
-
-
-class TransformationInput:
- """
- :param buffer_control: :class:`.BufferControl` instance.
- :param lineno: The number of the line to which we apply the processor.
- :param source_to_display: A function that returns the position in the
- `fragments` for any position in the source string. (This takes
- previous processors into account.)
- :param fragments: List of fragments that we can transform. (Received from the
- previous processor.)
- """
-
- def __init__(
- self,
- buffer_control: "BufferControl",
- document: Document,
- lineno: int,
- source_to_display: SourceToDisplay,
- fragments: StyleAndTextTuples,
- width: int,
- height: int,
- ) -> None:
-
- self.buffer_control = buffer_control
- self.document = document
- self.lineno = lineno
- self.source_to_display = source_to_display
- self.fragments = fragments
- self.width = width
- self.height = height
-
- def unpack(
- self,
- ) -> Tuple[
- "BufferControl", Document, int, SourceToDisplay, StyleAndTextTuples, int, int
- ]:
- return (
- self.buffer_control,
- self.document,
- self.lineno,
- self.source_to_display,
- self.fragments,
- self.width,
- self.height,
- )
-
-
-class Transformation:
- """
- Transformation result, as returned by :meth:`.Processor.apply_transformation`.
-
- Important: Always make sure that the length of `document.text` is equal to
- the length of all the text in `fragments`!
-
- :param fragments: The transformed fragments. To be displayed, or to pass to
- the next processor.
- :param source_to_display: Cursor position transformation from original
- string to transformed string.
- :param display_to_source: Cursor position transformed from source string to
- original string.
- """
-
- def __init__(
- self,
- fragments: StyleAndTextTuples,
- source_to_display: Optional[SourceToDisplay] = None,
- display_to_source: Optional[DisplayToSource] = None,
- ) -> None:
-
- self.fragments = fragments
- self.source_to_display = source_to_display or (lambda i: i)
- self.display_to_source = display_to_source or (lambda i: i)
-
-
-class DummyProcessor(Processor):
- """
- A `Processor` that doesn't do anything.
- """
-
- def apply_transformation(
- self, transformation_input: TransformationInput
- ) -> Transformation:
- return Transformation(transformation_input.fragments)
-
-
-class HighlightSearchProcessor(Processor):
- """
- Processor that highlights search matches in the document.
- Note that this doesn't support multiline search matches yet.
-
- The style classes 'search' and 'search.current' will be applied to the
- content.
- """
-
- _classname = "search"
- _classname_current = "search.current"
-
- def _get_search_text(self, buffer_control: "BufferControl") -> str:
- """
- The text we are searching for.
- """
- return buffer_control.search_state.text
-
- def apply_transformation(
- self, transformation_input: TransformationInput
- ) -> Transformation:
-
- (
- buffer_control,
- document,
- lineno,
- source_to_display,
- fragments,
- _,
- _,
- ) = transformation_input.unpack()
-
- search_text = self._get_search_text(buffer_control)
- searchmatch_fragment = f" class:{self._classname} "
- searchmatch_current_fragment = f" class:{self._classname_current} "
-
- if search_text and not get_app().is_done:
- # For each search match, replace the style string.
- line_text = fragment_list_to_text(fragments)
- fragments = explode_text_fragments(fragments)
-
- if buffer_control.search_state.ignore_case():
- flags = re.IGNORECASE
- else:
- flags = re.RegexFlag(0)
-
- # Get cursor column.
- cursor_column: Optional[int]
- if document.cursor_position_row == lineno:
- cursor_column = source_to_display(document.cursor_position_col)
- else:
- cursor_column = None
-
- for match in re.finditer(re.escape(search_text), line_text, flags=flags):
- if cursor_column is not None:
- on_cursor = match.start() <= cursor_column < match.end()
- else:
- on_cursor = False
-
- for i in range(match.start(), match.end()):
- old_fragment, text, *_ = fragments[i]
- if on_cursor:
- fragments[i] = (
- old_fragment + searchmatch_current_fragment,
- fragments[i][1],
- )
- else:
- fragments[i] = (
- old_fragment + searchmatch_fragment,
- fragments[i][1],
- )
-
- return Transformation(fragments)
-
-
-class HighlightIncrementalSearchProcessor(HighlightSearchProcessor):
- """
- Highlight the search terms that are used for highlighting the incremental
- search. The style class 'incsearch' will be applied to the content.
-
- Important: this requires the `preview_search=True` flag to be set for the
- `BufferControl`. Otherwise, the cursor position won't be set to the search
- match while searching, and nothing happens.
- """
-
- _classname = "incsearch"
- _classname_current = "incsearch.current"
-
- def _get_search_text(self, buffer_control: "BufferControl") -> str:
- """
- The text we are searching for.
- """
- # When the search buffer has focus, take that text.
- search_buffer = buffer_control.search_buffer
- if search_buffer is not None and search_buffer.text:
- return search_buffer.text
- return ""
-
-
-class HighlightSelectionProcessor(Processor):
- """
- Processor that highlights the selection in the document.
- """
-
- def apply_transformation(
- self, transformation_input: TransformationInput
- ) -> Transformation:
- (
- buffer_control,
- document,
- lineno,
- source_to_display,
- fragments,
- _,
- _,
- ) = transformation_input.unpack()
-
- selected_fragment = " class:selected "
-
- # In case of selection, highlight all matches.
- selection_at_line = document.selection_range_at_line(lineno)
-
- if selection_at_line:
- from_, to = selection_at_line
- from_ = source_to_display(from_)
- to = source_to_display(to)
-
- fragments = explode_text_fragments(fragments)
-
- if from_ == 0 and to == 0 and len(fragments) == 0:
- # When this is an empty line, insert a space in order to
- # visualise the selection.
- return Transformation([(selected_fragment, " ")])
- else:
- for i in range(from_, to):
- if i < len(fragments):
- old_fragment, old_text, *_ = fragments[i]
- fragments[i] = (old_fragment + selected_fragment, old_text)
- elif i == len(fragments):
- fragments.append((selected_fragment, " "))
-
- return Transformation(fragments)
-
-
-class PasswordProcessor(Processor):
- """
- Processor that masks the input. (For passwords.)
-
- :param char: (string) Character to be used. "*" by default.
- """
-
- def __init__(self, char: str = "*") -> None:
- self.char = char
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- fragments: StyleAndTextTuples = cast(
- StyleAndTextTuples,
- [
- (style, self.char * len(text), *handler)
- for style, text, *handler in ti.fragments
- ],
- )
-
- return Transformation(fragments)
-
-
-class HighlightMatchingBracketProcessor(Processor):
- """
- When the cursor is on or right after a bracket, it highlights the matching
- bracket.
-
- :param max_cursor_distance: Only highlight matching brackets when the
- cursor is within this distance. (From inside a `Processor`, we can't
- know which lines will be visible on the screen. But we also don't want
- to scan the whole document for matching brackets on each key press, so
- we limit to this value.)
- """
-
- _closing_braces = "])}>"
-
- def __init__(
- self, chars: str = "[](){}<>", max_cursor_distance: int = 1000
- ) -> None:
- self.chars = chars
- self.max_cursor_distance = max_cursor_distance
-
- self._positions_cache: SimpleCache[
- Hashable, List[Tuple[int, int]]
- ] = SimpleCache(maxsize=8)
-
- def _get_positions_to_highlight(self, document: Document) -> List[Tuple[int, int]]:
- """
- Return a list of (row, col) tuples that need to be highlighted.
- """
- pos: Optional[int]
-
- # 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: TransformationInput
- ) -> Transformation:
-
- (
- buffer_control,
- document,
- lineno,
- source_to_display,
- fragments,
- _,
- _,
- ) = transformation_input.unpack()
-
- # When the application is in the 'done' state, don't highlight.
- if get_app().is_done:
- return Transformation(fragments)
-
- # Get the highlight positions.
- key = (get_app().render_counter, document.text, document.cursor_position)
- positions = self._positions_cache.get(
- key, lambda: self._get_positions_to_highlight(document)
- )
-
- # Apply if positions were found at this line.
- if positions:
- for row, col in positions:
- if row == lineno:
- col = source_to_display(col)
- fragments = explode_text_fragments(fragments)
- style, text, *_ = fragments[col]
-
- if col == document.cursor_position_col:
- style += " class:matching-bracket.cursor "
- else:
- style += " class:matching-bracket.other "
-
- fragments[col] = (style, text)
-
- return Transformation(fragments)
-
-
-class DisplayMultipleCursors(Processor):
- """
- When we're in Vi block insert mode, display all the cursors.
- """
-
- def apply_transformation(
- self, transformation_input: TransformationInput
- ) -> Transformation:
-
- (
- buffer_control,
- document,
- lineno,
- source_to_display,
- fragments,
- _,
- _,
- ) = transformation_input.unpack()
-
- buff = buffer_control.buffer
-
- if vi_insert_multiple_mode():
- cursor_positions = buff.multiple_cursor_positions
- fragments = explode_text_fragments(fragments)
-
- # If any cursor appears on the current line, highlight that.
- start_pos = document.translate_row_col_to_index(lineno, 0)
- end_pos = start_pos + len(document.lines[lineno])
-
- fragment_suffix = " class:multiple-cursors"
-
- for p in cursor_positions:
- if start_pos <= p <= end_pos:
- column = source_to_display(p - start_pos)
-
- # Replace fragment.
- try:
- style, text, *_ = fragments[column]
- except IndexError:
- # Cursor needs to be displayed after the current text.
- fragments.append((fragment_suffix, " "))
- else:
- style += fragment_suffix
- fragments[column] = (style, text)
-
- return Transformation(fragments)
- else:
- return Transformation(fragments)
-
-
-class BeforeInput(Processor):
- """
- Insert text before the input.
-
- :param text: This can be either plain text or formatted text
- (or a callable that returns any of those).
- :param style: style to be applied to this prompt/prefix.
- """
-
- def __init__(self, text: AnyFormattedText, style: str = "") -> None:
- self.text = text
- self.style = style
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- source_to_display: Optional[SourceToDisplay]
- display_to_source: Optional[DisplayToSource]
-
- if ti.lineno == 0:
- # Get fragments.
- fragments_before = to_formatted_text(self.text, self.style)
- fragments = fragments_before + ti.fragments
-
- shift_position = fragment_list_len(fragments_before)
- source_to_display = lambda i: i + shift_position
- display_to_source = lambda i: i - shift_position
- else:
- fragments = ti.fragments
- source_to_display = None
- display_to_source = None
-
- return Transformation(
- fragments,
- source_to_display=source_to_display,
- display_to_source=display_to_source,
- )
-
- def __repr__(self) -> str:
- return f"BeforeInput({self.text!r}, {self.style!r})"
-
-
-class ShowArg(BeforeInput):
- """
- Display the 'arg' in front of the input.
-
- This was used by the `PromptSession`, but now it uses the
- `Window.get_line_prefix` function instead.
- """
-
- def __init__(self) -> None:
- super().__init__(self._get_text_fragments)
-
- def _get_text_fragments(self) -> StyleAndTextTuples:
- app = get_app()
- if app.key_processor.arg is None:
- return []
- else:
- arg = app.key_processor.arg
-
- return [
- ("class:prompt.arg", "(arg: "),
- ("class:prompt.arg.text", str(arg)),
- ("class:prompt.arg", ") "),
- ]
-
- def __repr__(self) -> str:
- return "ShowArg()"
-
-
-class AfterInput(Processor):
- """
- Insert text after the input.
-
- :param text: This can be either plain text or formatted text
- (or a callable that returns any of those).
- :param style: style to be applied to this prompt/prefix.
- """
-
- def __init__(self, text: AnyFormattedText, style: str = "") -> None:
- self.text = text
- self.style = style
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- # Insert fragments after the last line.
- if ti.lineno == ti.document.line_count - 1:
- # Get fragments.
- fragments_after = to_formatted_text(self.text, self.style)
- return Transformation(fragments=ti.fragments + fragments_after)
- else:
- return Transformation(fragments=ti.fragments)
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.text!r}, style={self.style!r})"
-
-
-class AppendAutoSuggestion(Processor):
- """
- Append the auto suggestion to the input.
- (The user can then press the right arrow the insert the suggestion.)
- """
-
- def __init__(self, style: str = "class:auto-suggestion") -> None:
- self.style = style
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- # Insert fragments after the last line.
- if ti.lineno == ti.document.line_count - 1:
- buffer = ti.buffer_control.buffer
-
- if buffer.suggestion and ti.document.is_cursor_at_the_end:
- suggestion = buffer.suggestion.text
- else:
- suggestion = ""
-
- return Transformation(fragments=ti.fragments + [(self.style, suggestion)])
- else:
- return Transformation(fragments=ti.fragments)
-
-
-class ShowLeadingWhiteSpaceProcessor(Processor):
- """
- Make leading whitespace visible.
-
- :param get_char: Callable that returns one character.
- """
-
- def __init__(
- self,
- get_char: Optional[Callable[[], str]] = None,
- style: str = "class:leading-whitespace",
- ) -> None:
- def default_get_char() -> str:
- if "\xb7".encode(get_app().output.encoding(), "replace") == b"?":
- return "."
- else:
- return "\xb7"
-
- self.style = style
- self.get_char = get_char or default_get_char
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- fragments = ti.fragments
-
- # Walk through all te fragments.
- if fragments and fragment_list_to_text(fragments).startswith(" "):
- t = (self.style, self.get_char())
- fragments = explode_text_fragments(fragments)
-
- for i in range(len(fragments)):
- if fragments[i][1] == " ":
- fragments[i] = t
- else:
- break
-
- return Transformation(fragments)
-
-
-class ShowTrailingWhiteSpaceProcessor(Processor):
- """
- Make trailing whitespace visible.
-
- :param get_char: Callable that returns one character.
- """
-
- def __init__(
- self,
- get_char: Optional[Callable[[], str]] = None,
- style: str = "class:training-whitespace",
- ) -> None:
- def default_get_char() -> str:
- if "\xb7".encode(get_app().output.encoding(), "replace") == b"?":
- return "."
- else:
- return "\xb7"
-
- self.style = style
- self.get_char = get_char or default_get_char
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- fragments = ti.fragments
-
- if fragments and fragments[-1][1].endswith(" "):
- t = (self.style, self.get_char())
- fragments = explode_text_fragments(fragments)
-
- # Walk backwards through all te fragments and replace whitespace.
- for i in range(len(fragments) - 1, -1, -1):
- char = fragments[i][1]
- if char == " ":
- fragments[i] = t
- else:
- break
-
- return Transformation(fragments)
-
-
-class TabsProcessor(Processor):
- """
- Render tabs as spaces (instead of ^I) or make them visible (for instance,
- by replacing them with dots.)
-
- :param tabstop: Horizontal space taken by a tab. (`int` or callable that
- returns an `int`).
- :param char1: Character or callable that returns a character (text of
- length one). This one is used for the first space taken by the tab.
- :param char2: Like `char1`, but for the rest of the space.
- """
-
- def __init__(
- self,
- tabstop: Union[int, Callable[[], int]] = 4,
- char1: Union[str, Callable[[], str]] = "|",
- char2: Union[str, Callable[[], str]] = "\u2508",
- style: str = "class:tab",
- ) -> None:
-
- self.char1 = char1
- self.char2 = char2
- self.tabstop = tabstop
- self.style = style
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- tabstop = to_int(self.tabstop)
- style = self.style
-
- # Create separator for tabs.
- separator1 = to_str(self.char1)
- separator2 = to_str(self.char2)
-
- # Transform fragments.
- fragments = explode_text_fragments(ti.fragments)
-
- position_mappings = {}
- result_fragments: StyleAndTextTuples = []
- pos = 0
-
- for i, fragment_and_text in enumerate(fragments):
- position_mappings[i] = pos
-
- if fragment_and_text[1] == "\t":
- # Calculate how many characters we have to insert.
- count = tabstop - (pos % tabstop)
- if count == 0:
- count = tabstop
-
- # Insert tab.
- result_fragments.append((style, separator1))
- result_fragments.append((style, separator2 * (count - 1)))
- pos += count
- else:
- result_fragments.append(fragment_and_text)
- pos += 1
-
- position_mappings[len(fragments)] = pos
- # Add `pos+1` to mapping, because the cursor can be right after the
- # line as well.
- position_mappings[len(fragments) + 1] = pos + 1
-
- def source_to_display(from_position: int) -> int:
- "Maps original cursor position to the new one."
- return position_mappings[from_position]
-
- def display_to_source(display_pos: int) -> int:
- "Maps display cursor position to the original one."
- position_mappings_reversed = {v: k for k, v in position_mappings.items()}
-
- while display_pos >= 0:
- try:
- return position_mappings_reversed[display_pos]
- except KeyError:
- display_pos -= 1
- return 0
-
- return Transformation(
- result_fragments,
- source_to_display=source_to_display,
- display_to_source=display_to_source,
- )
-
-
-class ReverseSearchProcessor(Processor):
- """
- Process to display the "(reverse-i-search)`...`:..." stuff around
- the search buffer.
-
- Note: This processor is meant to be applied to the BufferControl that
- contains the search buffer, it's not meant for the original input.
- """
-
- _excluded_input_processors: List[Type[Processor]] = [
- HighlightSearchProcessor,
- HighlightSelectionProcessor,
- BeforeInput,
- AfterInput,
- ]
-
- def _get_main_buffer(
- self, buffer_control: "BufferControl"
- ) -> Optional["BufferControl"]:
- from prompt_toolkit.layout.controls import BufferControl
-
- prev_control = get_app().layout.search_target_buffer_control
- if (
- isinstance(prev_control, BufferControl)
- and prev_control.search_buffer_control == buffer_control
- ):
- return prev_control
- return None
-
- def _content(
- self, main_control: "BufferControl", ti: TransformationInput
- ) -> "UIContent":
- from prompt_toolkit.layout.controls import BufferControl
-
- # Emulate the BufferControl through which we are searching.
- # For this we filter out some of the input processors.
- excluded_processors = tuple(self._excluded_input_processors)
-
- def filter_processor(item: Processor) -> Optional[Processor]:
- """Filter processors from the main control that we want to disable
- here. This returns either an accepted processor or None."""
- # For a `_MergedProcessor`, check each individual processor, recursively.
- if isinstance(item, _MergedProcessor):
- accepted_processors = [filter_processor(p) for p in item.processors]
- return merge_processors(
- [p for p in accepted_processors if p is not None]
- )
-
- # For a `ConditionalProcessor`, check the body.
- elif isinstance(item, ConditionalProcessor):
- p = filter_processor(item.processor)
- if p:
- return ConditionalProcessor(p, item.filter)
-
- # Otherwise, check the processor itself.
- else:
- if not isinstance(item, excluded_processors):
- return item
-
- return None
-
- filtered_processor = filter_processor(
- merge_processors(main_control.input_processors or [])
- )
- highlight_processor = HighlightIncrementalSearchProcessor()
-
- if filtered_processor:
- new_processors = [filtered_processor, highlight_processor]
- else:
- new_processors = [highlight_processor]
-
- from .controls import SearchBufferControl
-
- assert isinstance(ti.buffer_control, SearchBufferControl)
-
- buffer_control = BufferControl(
- buffer=main_control.buffer,
- input_processors=new_processors,
- include_default_input_processors=False,
- lexer=main_control.lexer,
- preview_search=True,
- search_buffer_control=ti.buffer_control,
- )
-
- return buffer_control.create_content(ti.width, ti.height, preview_search=True)
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- from .controls import SearchBufferControl
-
- assert isinstance(
- ti.buffer_control, SearchBufferControl
- ), "`ReverseSearchProcessor` should be applied to a `SearchBufferControl` only."
-
- source_to_display: Optional[SourceToDisplay]
- display_to_source: Optional[DisplayToSource]
-
- main_control = self._get_main_buffer(ti.buffer_control)
-
- if ti.lineno == 0 and main_control:
- content = self._content(main_control, ti)
-
- # Get the line from the original document for this search.
- line_fragments = content.get_line(content.cursor_position.y)
-
- if main_control.search_state.direction == SearchDirection.FORWARD:
- direction_text = "i-search"
- else:
- direction_text = "reverse-i-search"
-
- fragments_before: StyleAndTextTuples = [
- ("class:prompt.search", "("),
- ("class:prompt.search", direction_text),
- ("class:prompt.search", ")`"),
- ]
-
- fragments = (
- fragments_before
- + [
- ("class:prompt.search.text", fragment_list_to_text(ti.fragments)),
- ("", "': "),
- ]
- + line_fragments
- )
-
- shift_position = fragment_list_len(fragments_before)
- source_to_display = lambda i: i + shift_position
- display_to_source = lambda i: i - shift_position
- else:
- source_to_display = None
- display_to_source = None
- fragments = ti.fragments
-
- return Transformation(
- fragments,
- source_to_display=source_to_display,
- display_to_source=display_to_source,
- )
-
-
-class ConditionalProcessor(Processor):
- """
- Processor that applies another processor, according to a certain condition.
- Example::
-
- # Create a function that returns whether or not the processor should
- # currently be applied.
- def highlight_enabled():
- return true_or_false
-
- # Wrapped it in a `ConditionalProcessor` for usage in a `BufferControl`.
- BufferControl(input_processors=[
- ConditionalProcessor(HighlightSearchProcessor(),
- Condition(highlight_enabled))])
-
- :param processor: :class:`.Processor` instance.
- :param filter: :class:`~prompt_toolkit.filters.Filter` instance.
- """
-
- def __init__(self, processor: Processor, filter: FilterOrBool) -> None:
- self.processor = processor
- self.filter = to_filter(filter)
-
- def apply_transformation(
- self, transformation_input: TransformationInput
- ) -> Transformation:
- # Run processor when enabled.
- if self.filter():
- return self.processor.apply_transformation(transformation_input)
- else:
- return Transformation(transformation_input.fragments)
-
- def __repr__(self) -> str:
- return "{}(processor={!r}, filter={!r})".format(
- self.__class__.__name__,
- self.processor,
- self.filter,
- )
-
-
-class DynamicProcessor(Processor):
- """
- Processor class that dynamically returns any Processor.
-
- :param get_processor: Callable that returns a :class:`.Processor` instance.
- """
-
- def __init__(self, get_processor: Callable[[], Optional[Processor]]) -> None:
- self.get_processor = get_processor
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- processor = self.get_processor() or DummyProcessor()
- return processor.apply_transformation(ti)
-
-
-def merge_processors(processors: List[Processor]) -> Processor:
- """
- Merge multiple `Processor` objects into one.
- """
- if len(processors) == 0:
- return DummyProcessor()
-
- if len(processors) == 1:
- return processors[0] # Nothing to merge.
-
- return _MergedProcessor(processors)
-
-
-class _MergedProcessor(Processor):
- """
- Processor that groups multiple other `Processor` objects, but exposes an
- API as if it is one `Processor`.
- """
-
- def __init__(self, processors: List[Processor]):
- self.processors = processors
-
- def apply_transformation(self, ti: TransformationInput) -> Transformation:
- source_to_display_functions = [ti.source_to_display]
- display_to_source_functions = []
- fragments = ti.fragments
-
- def source_to_display(i: int) -> int:
- """Translate x position from the buffer to the x position in the
- processor fragments list."""
- for f in source_to_display_functions:
- i = f(i)
- return i
-
- for p in self.processors:
- transformation = p.apply_transformation(
- TransformationInput(
- ti.buffer_control,
- ti.document,
- ti.lineno,
- source_to_display,
- fragments,
- ti.width,
- ti.height,
- )
- )
- fragments = transformation.fragments
- display_to_source_functions.append(transformation.display_to_source)
- source_to_display_functions.append(transformation.source_to_display)
-
- def display_to_source(i: int) -> int:
- for f in reversed(display_to_source_functions):
- i = f(i)
- return i
-
- # In the case of a nested _MergedProcessor, each processor wants to
- # receive a 'source_to_display' function (as part of the
- # TransformationInput) that has everything in the chain before
- # included, because it can be called as part of the
- # `apply_transformation` function. However, this first
- # `source_to_display` should not be part of the output that we are
- # returning. (This is the most consistent with `display_to_source`.)
- del source_to_display_functions[:1]
-
- return Transformation(fragments, source_to_display, display_to_source)
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/screen.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/screen.py
deleted file mode 100644
index 8874fba6f88..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/screen.py
+++ /dev/null
@@ -1,328 +0,0 @@
-from collections import defaultdict
-from typing import TYPE_CHECKING, Callable, DefaultDict, Dict, List, Optional, Tuple
-
-from prompt_toolkit.cache import FastDictCache
-from prompt_toolkit.data_structures import Point
-from prompt_toolkit.utils import get_cwidth
-
-if TYPE_CHECKING:
- from .containers import Window
-
-
-__all__ = [
- "Screen",
- "Char",
-]
-
-
-class Char:
- """
- Represent a single character in a :class:`.Screen`.
-
- This should be considered immutable.
-
- :param char: A single character (can be a double-width character).
- :param style: A style string. (Can contain classnames.)
- """
-
- __slots__ = ("char", "style", "width")
-
- # If we end up having one of these special control sequences in the input string,
- # we should display them as follows:
- # Usually this happens after a "quoted insert".
- display_mappings: Dict[str, str] = {
- "\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": "^]",
- "\x1e": "^^",
- "\x1f": "^_",
- "\x7f": "^?", # ASCII Delete (backspace).
- # Special characters. All visualized like Vim does.
- "\x80": "<80>",
- "\x81": "<81>",
- "\x82": "<82>",
- "\x83": "<83>",
- "\x84": "<84>",
- "\x85": "<85>",
- "\x86": "<86>",
- "\x87": "<87>",
- "\x88": "<88>",
- "\x89": "<89>",
- "\x8a": "<8a>",
- "\x8b": "<8b>",
- "\x8c": "<8c>",
- "\x8d": "<8d>",
- "\x8e": "<8e>",
- "\x8f": "<8f>",
- "\x90": "<90>",
- "\x91": "<91>",
- "\x92": "<92>",
- "\x93": "<93>",
- "\x94": "<94>",
- "\x95": "<95>",
- "\x96": "<96>",
- "\x97": "<97>",
- "\x98": "<98>",
- "\x99": "<99>",
- "\x9a": "<9a>",
- "\x9b": "<9b>",
- "\x9c": "<9c>",
- "\x9d": "<9d>",
- "\x9e": "<9e>",
- "\x9f": "<9f>",
- # For the non-breaking space: visualize like Emacs does by default.
- # (Print a space, but attach the 'nbsp' class that applies the
- # underline style.)
- "\xa0": " ",
- }
-
- def __init__(self, char: str = " ", style: str = "") -> None:
- # If this character has to be displayed otherwise, take that one.
- if char in self.display_mappings:
- if char == "\xa0":
- style += " class:nbsp " # Will be underlined.
- else:
- style += " class:control-character "
-
- char = self.display_mappings[char]
-
- self.char = char
- self.style = style
-
- # Calculate width. (We always need this, so better to store it directly
- # as a member for performance.)
- self.width = get_cwidth(char)
-
- # In theory, `other` can be any type of object, but because of performance
- # we don't want to do an `isinstance` check every time. We assume "other"
- # is always a "Char".
- def _equal(self, other: "Char") -> bool:
- return self.char == other.char and self.style == other.style
-
- def _not_equal(self, other: "Char") -> bool:
- # Not equal: We don't do `not char.__eq__` here, because of the
- # performance of calling yet another function.
- return self.char != other.char or self.style != other.style
-
- if not TYPE_CHECKING:
- __eq__ = _equal
- __ne__ = _not_equal
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self.char!r}, {self.style!r})"
-
-
-_CHAR_CACHE: FastDictCache[Tuple[str, str], Char] = FastDictCache(
- Char, size=1000 * 1000
-)
-Transparent = "[transparent]"
-
-
-class Screen:
- """
- Two dimensional buffer of :class:`.Char` instances.
- """
-
- def __init__(
- self,
- default_char: Optional[Char] = None,
- initial_width: int = 0,
- initial_height: int = 0,
- ) -> None:
-
- if default_char is None:
- default_char2 = _CHAR_CACHE[" ", Transparent]
- else:
- default_char2 = default_char
-
- self.data_buffer: DefaultDict[int, DefaultDict[int, Char]] = defaultdict(
- lambda: defaultdict(lambda: default_char2)
- )
-
- #: Escape sequences to be injected.
- self.zero_width_escapes: DefaultDict[int, DefaultDict[int, str]] = defaultdict(
- lambda: defaultdict(lambda: "")
- )
-
- #: Position of the cursor.
- self.cursor_positions: Dict[
- "Window", Point
- ] = {} # Map `Window` objects to `Point` objects.
-
- #: Visibility of the cursor.
- self.show_cursor = True
-
- #: (Optional) Where to position the menu. E.g. at the start of a completion.
- #: (We can't use the cursor position, because we don't want the
- #: completion menu to change its position when we browse through all the
- #: completions.)
- self.menu_positions: Dict[
- "Window", Point
- ] = {} # Map `Window` objects to `Point` objects.
-
- #: Currently used width/height of the screen. This will increase when
- #: data is written to the screen.
- self.width = initial_width or 0
- self.height = initial_height or 0
-
- # Windows that have been drawn. (Each `Window` class will add itself to
- # this list.)
- self.visible_windows_to_write_positions: Dict["Window", "WritePosition"] = {}
-
- # List of (z_index, draw_func)
- self._draw_float_functions: List[Tuple[int, Callable[[], None]]] = []
-
- @property
- def visible_windows(self) -> List["Window"]:
- return list(self.visible_windows_to_write_positions.keys())
-
- def set_cursor_position(self, window: "Window", position: Point) -> None:
- """
- Set the cursor position for a given window.
- """
- self.cursor_positions[window] = position
-
- def set_menu_position(self, window: "Window", position: Point) -> None:
- """
- Set the cursor position for a given window.
- """
- self.menu_positions[window] = position
-
- def get_cursor_position(self, window: "Window") -> Point:
- """
- Get the cursor position for a given window.
- Returns a `Point`.
- """
- try:
- return self.cursor_positions[window]
- except KeyError:
- return Point(x=0, y=0)
-
- def get_menu_position(self, window: "Window") -> Point:
- """
- Get the menu position for a given window.
- (This falls back to the cursor position if no menu position was set.)
- """
- try:
- return self.menu_positions[window]
- except KeyError:
- try:
- return self.cursor_positions[window]
- except KeyError:
- return Point(x=0, y=0)
-
- def draw_with_z_index(self, z_index: int, draw_func: Callable[[], None]) -> None:
- """
- Add a draw-function for a `Window` which has a >= 0 z_index.
- This will be postponed until `draw_all_floats` is called.
- """
- self._draw_float_functions.append((z_index, draw_func))
-
- def draw_all_floats(self) -> None:
- """
- Draw all float functions in order of z-index.
- """
- # We keep looping because some draw functions could add new functions
- # to this list. See `FloatContainer`.
- while self._draw_float_functions:
- # Sort the floats that we have so far by z_index.
- functions = sorted(self._draw_float_functions, key=lambda item: item[0])
-
- # Draw only one at a time, then sort everything again. Now floats
- # might have been added.
- self._draw_float_functions = functions[1:]
- functions[0][1]()
-
- def append_style_to_content(self, style_str: str) -> None:
- """
- For all the characters in the screen.
- Set the style string to the given `style_str`.
- """
- b = self.data_buffer
- char_cache = _CHAR_CACHE
-
- append_style = " " + style_str
-
- for y, row in b.items():
- for x, char in row.items():
- b[y][x] = char_cache[char.char, char.style + append_style]
-
- def fill_area(
- self, write_position: "WritePosition", style: str = "", after: bool = False
- ) -> None:
- """
- Fill the content of this area, using the given `style`.
- The style is prepended before whatever was here before.
- """
- if not style.strip():
- return
-
- xmin = write_position.xpos
- xmax = write_position.xpos + write_position.width
- char_cache = _CHAR_CACHE
- data_buffer = self.data_buffer
-
- if after:
- append_style = " " + style
- prepend_style = ""
- else:
- append_style = ""
- prepend_style = style + " "
-
- for y in range(
- write_position.ypos, write_position.ypos + write_position.height
- ):
- row = data_buffer[y]
- for x in range(xmin, xmax):
- cell = row[x]
- row[x] = char_cache[
- cell.char, prepend_style + cell.style + append_style
- ]
-
-
-class WritePosition:
- def __init__(self, xpos: int, ypos: int, width: int, height: int) -> None:
- assert height >= 0
- assert width >= 0
- # xpos and ypos can be negative. (A float can be partially visible.)
-
- self.xpos = xpos
- self.ypos = ypos
- self.width = width
- self.height = height
-
- def __repr__(self) -> str:
- return "{}(x={!r}, y={!r}, width={!r}, height={!r})".format(
- self.__class__.__name__,
- self.xpos,
- self.ypos,
- self.width,
- self.height,
- )
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/scrollable_pane.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/scrollable_pane.py
deleted file mode 100644
index a5500d7f7ce..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/scrollable_pane.py
+++ /dev/null
@@ -1,493 +0,0 @@
-from typing import Dict, List, Optional
-
-from prompt_toolkit.data_structures import Point
-from prompt_toolkit.filters import FilterOrBool, to_filter
-from prompt_toolkit.key_binding import KeyBindingsBase
-from prompt_toolkit.mouse_events import MouseEvent
-
-from .containers import Container, ScrollOffsets
-from .dimension import AnyDimension, Dimension, sum_layout_dimensions, to_dimension
-from .mouse_handlers import MouseHandler, MouseHandlers
-from .screen import Char, Screen, WritePosition
-
-__all__ = ["ScrollablePane"]
-
-# Never go beyond this height, because performance will degrade.
-MAX_AVAILABLE_HEIGHT = 10_000
-
-
-class ScrollablePane(Container):
- """
- Container widget that exposes a larger virtual screen to its content and
- displays it in a vertical scrollbale region.
-
- Typically this is wrapped in a large `HSplit` container. Make sure in that
- case to not specify a `height` dimension of the `HSplit`, so that it will
- scale according to the content.
-
- .. note::
-
- If you want to display a completion menu for widgets in this
- `ScrollablePane`, then it's still a good practice to use a
- `FloatContainer` with a `CompletionsMenu` in a `Float` at the top-level
- of the layout hierarchy, rather then nesting a `FloatContainer` in this
- `ScrollablePane`. (Otherwise, it's possible that the completion menu
- is clipped.)
-
- :param content: The content container.
- :param scrolloffset: Try to keep the cursor within this distance from the
- top/bottom (left/right offset is not used).
- :param keep_cursor_visible: When `True`, automatically scroll the pane so
- that the cursor (of the focused window) is always visible.
- :param keep_focused_window_visible: When `True`, automatically scroll th e
- pane so that the focused window is visible, or as much visible as
- possible if it doen't completely fit the screen.
- :param max_available_height: Always constraint the height to this amount
- for performance reasons.
- :param width: When given, use this width instead of looking at the children.
- :param height: When given, use this height instead of looking at the children.
- :param show_scrollbar: When `True` display a scrollbar on the right.
- """
-
- def __init__(
- self,
- content: Container,
- scroll_offsets: Optional[ScrollOffsets] = None,
- keep_cursor_visible: FilterOrBool = True,
- keep_focused_window_visible: FilterOrBool = True,
- max_available_height: int = MAX_AVAILABLE_HEIGHT,
- width: AnyDimension = None,
- height: AnyDimension = None,
- show_scrollbar: FilterOrBool = True,
- display_arrows: FilterOrBool = True,
- up_arrow_symbol: str = "^",
- down_arrow_symbol: str = "v",
- ) -> None:
- self.content = content
- self.scroll_offsets = scroll_offsets or ScrollOffsets(top=1, bottom=1)
- self.keep_cursor_visible = to_filter(keep_cursor_visible)
- self.keep_focused_window_visible = to_filter(keep_focused_window_visible)
- self.max_available_height = max_available_height
- self.width = width
- self.height = height
- self.show_scrollbar = to_filter(show_scrollbar)
- self.display_arrows = to_filter(display_arrows)
- self.up_arrow_symbol = up_arrow_symbol
- self.down_arrow_symbol = down_arrow_symbol
-
- self.vertical_scroll = 0
-
- def __repr__(self) -> str:
- return f"ScrollablePane({self.content!r})"
-
- def reset(self) -> None:
- self.content.reset()
-
- def preferred_width(self, max_available_width: int) -> Dimension:
- if self.width is not None:
- return to_dimension(self.width)
-
- # We're only scrolling vertical. So the preferred width is equal to
- # that of the content.
- content_width = self.content.preferred_width(max_available_width)
-
- # If a scrollbar needs to be displayed, add +1 to the content width.
- if self.show_scrollbar():
- return sum_layout_dimensions([Dimension.exact(1), content_width])
-
- return content_width
-
- def preferred_height(self, width: int, max_available_height: int) -> Dimension:
- if self.height is not None:
- return to_dimension(self.height)
-
- # Prefer a height large enough so that it fits all the content. If not,
- # we'll make the pane scrollable.
- if self.show_scrollbar():
- # If `show_scrollbar` is set. Always reserve space for the scrollbar.
- width -= 1
-
- dimension = self.content.preferred_height(width, self.max_available_height)
-
- # Only take 'preferred' into account. Min/max can be anything.
- return Dimension(min=0, preferred=dimension.preferred)
-
- def write_to_screen(
- self,
- screen: Screen,
- mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- parent_style: str,
- erase_bg: bool,
- z_index: Optional[int],
- ) -> None:
- """
- Render scrollable pane content.
-
- This works by rendering on an off-screen canvas, and copying over the
- visible region.
- """
- show_scrollbar = self.show_scrollbar()
-
- if show_scrollbar:
- virtual_width = write_position.width - 1
- else:
- virtual_width = write_position.width
-
- # Compute preferred height again.
- virtual_height = self.content.preferred_height(
- virtual_width, self.max_available_height
- ).preferred
-
- # Ensure virtual height is at least the available height.
- virtual_height = max(virtual_height, write_position.height)
- virtual_height = min(virtual_height, self.max_available_height)
-
- # First, write the content to a virtual screen, then copy over the
- # visible part to the real screen.
- temp_screen = Screen(default_char=Char(char=" ", style=parent_style))
- temp_write_position = WritePosition(
- xpos=0, ypos=0, width=virtual_width, height=virtual_height
- )
-
- temp_mouse_handlers = MouseHandlers()
-
- self.content.write_to_screen(
- temp_screen,
- temp_mouse_handlers,
- temp_write_position,
- parent_style,
- erase_bg,
- z_index,
- )
- temp_screen.draw_all_floats()
-
- # If anything in the virtual screen is focused, move vertical scroll to
- from prompt_toolkit.application import get_app
-
- focused_window = get_app().layout.current_window
-
- try:
- visible_win_write_pos = temp_screen.visible_windows_to_write_positions[
- focused_window
- ]
- except KeyError:
- pass # No window focused here. Don't scroll.
- else:
- # Make sure this window is visible.
- self._make_window_visible(
- write_position.height,
- virtual_height,
- visible_win_write_pos,
- temp_screen.cursor_positions.get(focused_window),
- )
-
- # Copy over virtual screen and zero width escapes to real screen.
- self._copy_over_screen(screen, temp_screen, write_position, virtual_width)
-
- # Copy over mouse handlers.
- self._copy_over_mouse_handlers(
- mouse_handlers, temp_mouse_handlers, write_position, virtual_width
- )
-
- # Set screen.width/height.
- ypos = write_position.ypos
- xpos = write_position.xpos
-
- screen.width = max(screen.width, xpos + virtual_width)
- screen.height = max(screen.height, ypos + write_position.height)
-
- # Copy over window write positions.
- self._copy_over_write_positions(screen, temp_screen, write_position)
-
- if temp_screen.show_cursor:
- screen.show_cursor = True
-
- # Copy over cursor positions, if they are visible.
- for window, point in temp_screen.cursor_positions.items():
- if (
- 0 <= point.x < write_position.width
- and self.vertical_scroll
- <= point.y
- < write_position.height + self.vertical_scroll
- ):
- screen.cursor_positions[window] = Point(
- x=point.x + xpos, y=point.y + ypos - self.vertical_scroll
- )
-
- # Copy over menu positions, but clip them to the visible area.
- for window, point in temp_screen.menu_positions.items():
- screen.menu_positions[window] = self._clip_point_to_visible_area(
- Point(x=point.x + xpos, y=point.y + ypos - self.vertical_scroll),
- write_position,
- )
-
- # Draw scrollbar.
- if show_scrollbar:
- self._draw_scrollbar(
- write_position,
- virtual_height,
- screen,
- )
-
- def _clip_point_to_visible_area(
- self, point: Point, write_position: WritePosition
- ) -> Point:
- """
- Ensure that the cursor and menu positions always are always reported
- """
- if point.x < write_position.xpos:
- point = point._replace(x=write_position.xpos)
- if point.y < write_position.ypos:
- point = point._replace(y=write_position.ypos)
- if point.x >= write_position.xpos + write_position.width:
- point = point._replace(x=write_position.xpos + write_position.width - 1)
- if point.y >= write_position.ypos + write_position.height:
- point = point._replace(y=write_position.ypos + write_position.height - 1)
-
- return point
-
- def _copy_over_screen(
- self,
- screen: Screen,
- temp_screen: Screen,
- write_position: WritePosition,
- virtual_width: int,
- ) -> None:
- """
- Copy over visible screen content and "zero width escape sequences".
- """
- ypos = write_position.ypos
- xpos = write_position.xpos
-
- for y in range(write_position.height):
- temp_row = temp_screen.data_buffer[y + self.vertical_scroll]
- row = screen.data_buffer[y + ypos]
- temp_zero_width_escapes = temp_screen.zero_width_escapes[
- y + self.vertical_scroll
- ]
- zero_width_escapes = screen.zero_width_escapes[y + ypos]
-
- for x in range(virtual_width):
- row[x + xpos] = temp_row[x]
-
- if x in temp_zero_width_escapes:
- zero_width_escapes[x + xpos] = temp_zero_width_escapes[x]
-
- def _copy_over_mouse_handlers(
- self,
- mouse_handlers: MouseHandlers,
- temp_mouse_handlers: MouseHandlers,
- write_position: WritePosition,
- virtual_width: int,
- ) -> None:
- """
- Copy over mouse handlers from virtual screen to real screen.
-
- Note: we take `virtual_width` because we don't want to copy over mouse
- handlers that we possibly have behind the scrollbar.
- """
- ypos = write_position.ypos
- xpos = write_position.xpos
-
- # Cache mouse handlers when wrapping them. Very often the same mouse
- # handler is registered for many positions.
- mouse_handler_wrappers: Dict[MouseHandler, MouseHandler] = {}
-
- def wrap_mouse_handler(handler: MouseHandler) -> MouseHandler:
- "Wrap mouse handler. Translate coordinates in `MouseEvent`."
- if handler not in mouse_handler_wrappers:
-
- def new_handler(event: MouseEvent) -> None:
- new_event = MouseEvent(
- position=Point(
- x=event.position.x - xpos,
- y=event.position.y + self.vertical_scroll - ypos,
- ),
- event_type=event.event_type,
- button=event.button,
- modifiers=event.modifiers,
- )
- handler(new_event)
-
- mouse_handler_wrappers[handler] = new_handler
- return mouse_handler_wrappers[handler]
-
- # Copy handlers.
- mouse_handlers_dict = mouse_handlers.mouse_handlers
- temp_mouse_handlers_dict = temp_mouse_handlers.mouse_handlers
-
- for y in range(write_position.height):
- if y in temp_mouse_handlers_dict:
- temp_mouse_row = temp_mouse_handlers_dict[y + self.vertical_scroll]
- mouse_row = mouse_handlers_dict[y + ypos]
- for x in range(virtual_width):
- if x in temp_mouse_row:
- mouse_row[x + xpos] = wrap_mouse_handler(temp_mouse_row[x])
-
- def _copy_over_write_positions(
- self, screen: Screen, temp_screen: Screen, write_position: WritePosition
- ) -> None:
- """
- Copy over window write positions.
- """
- ypos = write_position.ypos
- xpos = write_position.xpos
-
- for win, write_pos in temp_screen.visible_windows_to_write_positions.items():
- screen.visible_windows_to_write_positions[win] = WritePosition(
- xpos=write_pos.xpos + xpos,
- ypos=write_pos.ypos + ypos - self.vertical_scroll,
- # TODO: if the window is only partly visible, then truncate width/height.
- # This could be important if we have nested ScrollablePanes.
- height=write_pos.height,
- width=write_pos.width,
- )
-
- def is_modal(self) -> bool:
- return self.content.is_modal()
-
- def get_key_bindings(self) -> Optional[KeyBindingsBase]:
- return self.content.get_key_bindings()
-
- def get_children(self) -> List["Container"]:
- return [self.content]
-
- def _make_window_visible(
- self,
- visible_height: int,
- virtual_height: int,
- visible_win_write_pos: WritePosition,
- cursor_position: Optional[Point],
- ) -> None:
- """
- Scroll the scrollable pane, so that this window becomes visible.
-
- :param visible_height: Height of this `ScrollablePane` that is rendered.
- :param virtual_height: Height of the virtual, temp screen.
- :param visible_win_write_pos: `WritePosition` of the nested window on the
- temp screen.
- :param cursor_position: The location of the cursor position of this
- window on the temp screen.
- """
- # Start with maximum allowed scroll range, and then reduce according to
- # the focused window and cursor position.
- min_scroll = 0
- max_scroll = virtual_height - visible_height
-
- if self.keep_cursor_visible():
- # Reduce min/max scroll according to the cursor in the focused window.
- if cursor_position is not None:
- offsets = self.scroll_offsets
- cpos_min_scroll = (
- cursor_position.y - visible_height + 1 + offsets.bottom
- )
- cpos_max_scroll = cursor_position.y - offsets.top
- min_scroll = max(min_scroll, cpos_min_scroll)
- max_scroll = max(0, min(max_scroll, cpos_max_scroll))
-
- if self.keep_focused_window_visible():
- # Reduce min/max scroll according to focused window position.
- # If the window is small enough, bot the top and bottom of the window
- # should be visible.
- if visible_win_write_pos.height <= visible_height:
- window_min_scroll = (
- visible_win_write_pos.ypos
- + visible_win_write_pos.height
- - visible_height
- )
- window_max_scroll = visible_win_write_pos.ypos
- else:
- # Window does not fit on the screen. Make sure at least the whole
- # screen is occupied with this window, and nothing else is shown.
- window_min_scroll = visible_win_write_pos.ypos
- window_max_scroll = (
- visible_win_write_pos.ypos
- + visible_win_write_pos.height
- - visible_height
- )
-
- min_scroll = max(min_scroll, window_min_scroll)
- max_scroll = min(max_scroll, window_max_scroll)
-
- if min_scroll > max_scroll:
- min_scroll = max_scroll # Should not happen.
-
- # Finally, properly clip the vertical scroll.
- if self.vertical_scroll > max_scroll:
- self.vertical_scroll = max_scroll
- if self.vertical_scroll < min_scroll:
- self.vertical_scroll = min_scroll
-
- def _draw_scrollbar(
- self, write_position: WritePosition, content_height: int, screen: Screen
- ) -> None:
- """
- Draw the scrollbar on the screen.
-
- Note: There is some code duplication with the `ScrollbarMargin`
- implementation.
- """
-
- window_height = write_position.height
- display_arrows = self.display_arrows()
-
- if display_arrows:
- window_height -= 2
-
- try:
- fraction_visible = write_position.height / float(content_height)
- fraction_above = self.vertical_scroll / float(content_height)
-
- scrollbar_height = int(
- min(window_height, max(1, window_height * fraction_visible))
- )
- scrollbar_top = int(window_height * fraction_above)
- except ZeroDivisionError:
- return
- else:
-
- def is_scroll_button(row: int) -> bool:
- "True if we should display a button on this row."
- return scrollbar_top <= row <= scrollbar_top + scrollbar_height
-
- xpos = write_position.xpos + write_position.width - 1
- ypos = write_position.ypos
- data_buffer = screen.data_buffer
-
- # Up arrow.
- if display_arrows:
- data_buffer[ypos][xpos] = Char(
- self.up_arrow_symbol, "class:scrollbar.arrow"
- )
- ypos += 1
-
- # Scrollbar body.
- scrollbar_background = "class:scrollbar.background"
- scrollbar_background_start = "class:scrollbar.background,scrollbar.start"
- scrollbar_button = "class:scrollbar.button"
- scrollbar_button_end = "class:scrollbar.button,scrollbar.end"
-
- for i in range(window_height):
- style = ""
- if is_scroll_button(i):
- if not is_scroll_button(i + 1):
- # Give the last cell a different style, because we want
- # to underline this.
- style = scrollbar_button_end
- else:
- style = scrollbar_button
- else:
- if is_scroll_button(i + 1):
- style = scrollbar_background_start
- else:
- style = scrollbar_background
-
- data_buffer[ypos][xpos] = Char(" ", style)
- ypos += 1
-
- # Down arrow
- if display_arrows:
- data_buffer[ypos][xpos] = Char(
- self.down_arrow_symbol, "class:scrollbar.arrow"
- )
diff --git a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/utils.py b/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/utils.py
deleted file mode 100644
index 2e0f34388ba..00000000000
--- a/contrib/python/prompt-toolkit/py3/prompt_toolkit/layout/utils.py
+++ /dev/null
@@ -1,80 +0,0 @@
-from typing import TYPE_CHECKING, Iterable, List, TypeVar, Union, cast, overload
-
-from prompt_toolkit.formatted_text.base import OneStyleAndTextTuple
-
-if TYPE_CHECKING:
- from typing_extensions import SupportsIndex
-
-__all__ = [
- "explode_text_fragments",
-]
-
-_T = TypeVar("_T", bound=OneStyleAndTextTuple)
-
-
-class _ExplodedList(List[_T]):
- """
- Wrapper around a list, that marks it as 'exploded'.
-
- As soon as items are added or the list is extended, the new items are
- automatically exploded as well.
- """
-
- exploded = True
-
- def append(self, item: _T) -> None:
- self.extend([item])
-
- def extend(self, lst: Iterable[_T]) -> None:
- super().extend(explode_text_fragments(lst))
-
- def insert(self, index: "SupportsIndex", item: _T) -> None:
- raise NotImplementedError # TODO
-
- # TODO: When creating a copy() or [:], return also an _ExplodedList.
-
- @overload
- def __setitem__(self, index: "SupportsIndex", value: _T) -> None:
- ...
-
- @overload
- def __setitem__(self, index: slice, value: Iterable[_T]) -> None:
- ...
-
- def __setitem__(
- self, index: Union["SupportsIndex", slice], value: Union[_T, Iterable[_T]]
- ) -> None:
- """
- Ensure that when `(style_str, 'long string')` is set, the string will be
- exploded.
- """
- if not isinstance(index, slice):
- int_index = index.__index__()
- index = slice(int_index, int_index + 1)
- if isinstance(value, tuple): # In case of `OneStyleAndTextTuple`.
- value = cast("List[_T]", [value])
-
- super().__setitem__(index, explode_text_fragments(cast("Iterable[_T]", value)))
-
-
-def explode_text_fragments(fragments: Iterable[_T]) -> _ExplodedList[_T]:
- """
- Turn a list of (style_str, text) tuples into another list where each string is
- exactly one character.
-
- It should be fine to call this function several times. Calling this on a
- list that is already exploded, is a null operation.
-
- :param fragments: List of (style, text) tuples.
- """
- # When the fragments is already exploded, don't explode again.
- if isinstance(fragments, _ExplodedList):
- return fragments
-
- result: List[_T] = []
-
- for style, string, *rest in fragments: # type: ignore
- for c in string: # type: ignore
- result.append((style, c, *rest)) # type: ignore
-
- return _ExplodedList(result)