|  | """ | 
					
						
						|  | Container for the layout. | 
					
						
						|  | (Containers can contain other containers or user interface controls.) | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  | from __future__ import annotations | 
					
						
						|  |  | 
					
						
						|  | from abc import ABCMeta, abstractmethod | 
					
						
						|  | from enum import Enum | 
					
						
						|  | from functools import partial | 
					
						
						|  | from typing import TYPE_CHECKING, Callable, Sequence, 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: int | None, | 
					
						
						|  | ) -> 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) -> KeyBindingsBase | None: | 
					
						
						|  | """ | 
					
						
						|  | 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: Container | None = None, | 
					
						
						|  | padding: AnyDimension = Dimension.exact(0), | 
					
						
						|  | padding_char: str | None = None, | 
					
						
						|  | padding_style: str = "", | 
					
						
						|  | width: AnyDimension = None, | 
					
						
						|  | height: AnyDimension = None, | 
					
						
						|  | z_index: int | None = None, | 
					
						
						|  | modal: bool = False, | 
					
						
						|  | key_bindings: KeyBindingsBase | None = None, | 
					
						
						|  | style: 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) -> KeyBindingsBase | None: | 
					
						
						|  | 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: Container | None = None, | 
					
						
						|  | align: VerticalAlign = VerticalAlign.JUSTIFY, | 
					
						
						|  | padding: AnyDimension = 0, | 
					
						
						|  | padding_char: str | None = None, | 
					
						
						|  | padding_style: str = "", | 
					
						
						|  | width: AnyDimension = None, | 
					
						
						|  | height: AnyDimension = None, | 
					
						
						|  | z_index: int | None = None, | 
					
						
						|  | modal: bool = False, | 
					
						
						|  | key_bindings: KeyBindingsBase | None = None, | 
					
						
						|  | style: 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() | 
					
						
						|  |  | 
					
						
						|  | 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] = [] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM): | 
					
						
						|  | result.append(Window(width=Dimension(preferred=0))) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: int | None, | 
					
						
						|  | ) -> 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) -> list[int] | None: | 
					
						
						|  | """ | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | dimensions = [c.preferred_height(width, height) for c in self._all_children] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | sum_dimensions = sum_layout_dimensions(dimensions) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if sum_dimensions.min > height: | 
					
						
						|  | return None | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: Container | None = None, | 
					
						
						|  | align: HorizontalAlign = HorizontalAlign.JUSTIFY, | 
					
						
						|  | padding: AnyDimension = 0, | 
					
						
						|  | padding_char: str | None = None, | 
					
						
						|  | padding_style: str = "", | 
					
						
						|  | width: AnyDimension = None, | 
					
						
						|  | height: AnyDimension = None, | 
					
						
						|  | z_index: int | None = None, | 
					
						
						|  | modal: bool = False, | 
					
						
						|  | key_bindings: KeyBindingsBase | None = None, | 
					
						
						|  | style: 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() | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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] = [] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT): | 
					
						
						|  | result.append(Window(width=Dimension(preferred=0))) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) -> list[int] | None: | 
					
						
						|  | """ | 
					
						
						|  | Return the widths for all columns. | 
					
						
						|  | Or None when there is not enough space. | 
					
						
						|  | """ | 
					
						
						|  | children = self._all_children | 
					
						
						|  |  | 
					
						
						|  | if not children: | 
					
						
						|  | return [] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | dimensions = [c.preferred_width(width) for c in children] | 
					
						
						|  | preferred_dimensions = [d.preferred for d in dimensions] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | sum_dimensions = sum_layout_dimensions(dimensions) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if sum_dimensions.min > width: | 
					
						
						|  | return None | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: int | None, | 
					
						
						|  | ) -> 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 sizes is None: | 
					
						
						|  | self.window_too_small.write_to_screen( | 
					
						
						|  | screen, mouse_handlers, write_position, style, erase_bg, z_index | 
					
						
						|  | ) | 
					
						
						|  | return | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: KeyBindingsBase | None = None, | 
					
						
						|  | style: str | Callable[[], str] = "", | 
					
						
						|  | z_index: int | None = 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: int | None, | 
					
						
						|  | ) -> 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): | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | new_z_index = (z_index or 0) + fl.z_index | 
					
						
						|  | style = parent_style + " " + to_str(self.style) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | postpone = fl.xcursor is not None or fl.ycursor is not None | 
					
						
						|  |  | 
					
						
						|  | if postpone: | 
					
						
						|  | new_z_index = ( | 
					
						
						|  | number + 10**8 | 
					
						
						|  | ) | 
					
						
						|  | 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: int | None, | 
					
						
						|  | ) -> None: | 
					
						
						|  | "Draw a single Float." | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if fl.left is not None and fl_width is not None: | 
					
						
						|  | xpos = fl.left | 
					
						
						|  | width = fl_width | 
					
						
						|  |  | 
					
						
						|  | elif fl.left is not None and fl.right is not None: | 
					
						
						|  | xpos = fl.left | 
					
						
						|  | width = write_position.width - fl.left - fl.right | 
					
						
						|  |  | 
					
						
						|  | elif fl_width is not None and fl.right is not None: | 
					
						
						|  | xpos = write_position.width - fl.right - fl_width | 
					
						
						|  | width = fl_width | 
					
						
						|  |  | 
					
						
						|  | elif fl.xcursor: | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  | elif fl_width: | 
					
						
						|  | xpos = int((write_position.width - fl_width) / 2) | 
					
						
						|  | width = fl_width | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  | xpos = max(0, int((write_position.width - width) / 2)) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | width = min(width, write_position.width - xpos) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if fl.top is not None and fl_height is not None: | 
					
						
						|  | ypos = fl.top | 
					
						
						|  | height = fl_height | 
					
						
						|  |  | 
					
						
						|  | elif fl.top is not None and fl.bottom is not None: | 
					
						
						|  | ypos = fl.top | 
					
						
						|  | height = write_position.height - fl.top - fl.bottom | 
					
						
						|  |  | 
					
						
						|  | elif fl_height is not None and fl.bottom is not None: | 
					
						
						|  | ypos = write_position.height - fl_height - fl.bottom | 
					
						
						|  | height = fl_height | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if height > write_position.height - ypos: | 
					
						
						|  | if write_position.height - ypos + 1 >= ypos: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | height = write_position.height - ypos | 
					
						
						|  | else: | 
					
						
						|  |  | 
					
						
						|  | height = min(height, cursor_position.y) | 
					
						
						|  | ypos = cursor_position.y - height | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | elif fl_height: | 
					
						
						|  | ypos = int((write_position.height - fl_height) / 2) | 
					
						
						|  | height = fl_height | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  | ypos = max(0, int((write_position.height - height) / 2)) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | height = min(height, write_position.height - ypos) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) -> KeyBindingsBase | None: | 
					
						
						|  | 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: int | None = None, | 
					
						
						|  | right: int | None = None, | 
					
						
						|  | bottom: int | None = None, | 
					
						
						|  | left: int | None = None, | 
					
						
						|  | width: int | Callable[[], int] | None = None, | 
					
						
						|  | height: int | Callable[[], int] | None = None, | 
					
						
						|  | xcursor: bool = False, | 
					
						
						|  | ycursor: bool = False, | 
					
						
						|  | attach_to_window: AnyContainer | None = 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) -> int | None: | 
					
						
						|  | if callable(self.width): | 
					
						
						|  | return self.width() | 
					
						
						|  | return self.width | 
					
						
						|  |  | 
					
						
						|  | def get_height(self) -> int | None: | 
					
						
						|  | if callable(self.height): | 
					
						
						|  | return self.height() | 
					
						
						|  | return self.height | 
					
						
						|  |  | 
					
						
						|  | def __repr__(self) -> str: | 
					
						
						|  | return f"Float(content={self.content!r})" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  | 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, | 
					
						
						|  | ), | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: int | Callable[[], int] = 0, | 
					
						
						|  | bottom: int | Callable[[], int] = 0, | 
					
						
						|  | left: int | Callable[[], int] = 0, | 
					
						
						|  | right: 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 f"ScrollOffsets(top={self._top!r}, bottom={self._bottom!r}, left={self._left!r}, right={self._right!r})" | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: UIControl | None = None, | 
					
						
						|  | width: AnyDimension = None, | 
					
						
						|  | height: AnyDimension = None, | 
					
						
						|  | z_index: int | None = None, | 
					
						
						|  | dont_extend_width: FilterOrBool = False, | 
					
						
						|  | dont_extend_height: FilterOrBool = False, | 
					
						
						|  | ignore_content_width: FilterOrBool = False, | 
					
						
						|  | ignore_content_height: FilterOrBool = False, | 
					
						
						|  | left_margins: Sequence[Margin] | None = None, | 
					
						
						|  | right_margins: Sequence[Margin] | None = None, | 
					
						
						|  | scroll_offsets: ScrollOffsets | None = None, | 
					
						
						|  | allow_scroll_beyond_bottom: FilterOrBool = False, | 
					
						
						|  | wrap_lines: FilterOrBool = False, | 
					
						
						|  | get_vertical_scroll: Callable[[Window], int] | None = None, | 
					
						
						|  | get_horizontal_scroll: Callable[[Window], int] | None = None, | 
					
						
						|  | always_hide_cursor: FilterOrBool = False, | 
					
						
						|  | cursorline: FilterOrBool = False, | 
					
						
						|  | cursorcolumn: FilterOrBool = False, | 
					
						
						|  | colorcolumns: ( | 
					
						
						|  | None | list[ColorColumn] | Callable[[], list[ColorColumn]] | 
					
						
						|  | ) = None, | 
					
						
						|  | align: WindowAlign | Callable[[], WindowAlign] = WindowAlign.LEFT, | 
					
						
						|  | style: str | Callable[[], str] = "", | 
					
						
						|  | char: None | str | Callable[[], str] = None, | 
					
						
						|  | get_line_prefix: GetLinePrefixCallable | None = 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 f"Window(content={self.content!r})" | 
					
						
						|  |  | 
					
						
						|  | def reset(self) -> None: | 
					
						
						|  | self.content.reset() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self.vertical_scroll = 0 | 
					
						
						|  | self.horizontal_scroll = 0 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self.vertical_scroll_2 = 0 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self.render_info: WindowRenderInfo | None = None | 
					
						
						|  |  | 
					
						
						|  | def _get_margin_width(self, margin: Margin) -> int: | 
					
						
						|  | """ | 
					
						
						|  | Return the width for this margin. | 
					
						
						|  | (Calculate only once per render time.) | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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() -> int | None: | 
					
						
						|  | """Content width: is only calculated if no exact width for the | 
					
						
						|  | window was given.""" | 
					
						
						|  | if self.ignore_content_width(): | 
					
						
						|  | return None | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | total_margin_width = self._get_total_margin_width() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | preferred_width = self.content.preferred_width( | 
					
						
						|  | max_available_width - total_margin_width | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | if preferred_width is not None: | 
					
						
						|  |  | 
					
						
						|  | preferred_width += total_margin_width | 
					
						
						|  | return preferred_width | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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() -> int | None: | 
					
						
						|  | """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: Dimension | None, | 
					
						
						|  | get_preferred: Callable[[], int | None], | 
					
						
						|  | 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() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | preferred: int | None | 
					
						
						|  |  | 
					
						
						|  | if dimension.preferred_specified: | 
					
						
						|  | preferred = dimension.preferred | 
					
						
						|  | else: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | preferred = get_preferred() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if preferred is not None: | 
					
						
						|  | if dimension.max_specified: | 
					
						
						|  | preferred = min(preferred, dimension.max) | 
					
						
						|  |  | 
					
						
						|  | if dimension.min_specified: | 
					
						
						|  | preferred = max(preferred, dimension.min) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | max_: int | None | 
					
						
						|  | min_: int | None | 
					
						
						|  |  | 
					
						
						|  | 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) -> str | None: | 
					
						
						|  | "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: int | None, | 
					
						
						|  | ) -> None: | 
					
						
						|  | """ | 
					
						
						|  | Write window to screen. This renders the user control, the margins and | 
					
						
						|  | copies everything over to the absolute position at the given screen. | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  | draw_func() | 
					
						
						|  | else: | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if write_position.height <= 0 or write_position.width <= 0: | 
					
						
						|  | return | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | ui_content = self.content.create_content( | 
					
						
						|  | write_position.width - total_margin_width, write_position.height | 
					
						
						|  | ) | 
					
						
						|  | assert isinstance(ui_content, UIContent) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | wrap_lines = self.wrap_lines() | 
					
						
						|  | self._scroll( | 
					
						
						|  | ui_content, write_position.width - total_margin_width, write_position.height | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self._fill_bg(screen, write_position, erase_bg) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | align = self.align() if callable(self.align) else self.align | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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. | 
					
						
						|  | """ | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if self not in get_app().layout.walk_through_modal_area(): | 
					
						
						|  | return NotImplemented | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | yx_to_rowcol = {v: k for k, v in rowcol_to_yx.items()} | 
					
						
						|  | y = mouse_event.position.y | 
					
						
						|  | x = mouse_event.position.x | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | x -= 1 | 
					
						
						|  | else: | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 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, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | move_x = 0 | 
					
						
						|  |  | 
					
						
						|  | def render_margin(m: Margin, width: int) -> UIContent: | 
					
						
						|  | "Render margin. Return `Screen`." | 
					
						
						|  |  | 
					
						
						|  | fragments = m.create_margin(render_info, width, write_position.height) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | return FormattedTextControl(fragments).create_content( | 
					
						
						|  | width + 1, write_position.height | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | for m, width in zip(self.left_margins, left_margin_widths): | 
					
						
						|  | if width > 0: | 
					
						
						|  |  | 
					
						
						|  | margin_content = render_margin(m, width) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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): | 
					
						
						|  |  | 
					
						
						|  | margin_content = render_margin(m, width) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self._copy_margin(margin_content, screen, write_position, move_x, width) | 
					
						
						|  | move_x += width | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self._apply_style(screen, write_position, parent_style) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: Callable[[int, int], AnyFormattedText] | None = 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["", ""] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | visible_line_to_row_col: dict[int, tuple[int, int]] = {} | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 = {} | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | skipped = 0 | 
					
						
						|  | 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] | 
					
						
						|  |  | 
					
						
						|  | x -= h_scroll | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if x >= 0 and y >= 0 and x < width: | 
					
						
						|  | new_buffer_row[x + xpos] = char | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if char_width > 1: | 
					
						
						|  | for i in range(1, char_width): | 
					
						
						|  | new_buffer_row[x + xpos + i] = empty_char | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | elif char_width == 0: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | for pw in [2, 1]: | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | current_rowcol_to_yx[lineno, col + skipped] = ( | 
					
						
						|  | y + ypos, | 
					
						
						|  | x + xpos, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | col += 1 | 
					
						
						|  | x += char_width | 
					
						
						|  | return x, y | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def copy() -> int: | 
					
						
						|  | y = -vertical_scroll_2 | 
					
						
						|  | lineno = vertical_scroll | 
					
						
						|  |  | 
					
						
						|  | while y < write_position.height and lineno < line_count: | 
					
						
						|  |  | 
					
						
						|  | line = ui_content.get_line(lineno) | 
					
						
						|  |  | 
					
						
						|  | visible_line_to_row_col[y] = (lineno, horizontal_scroll) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | return Point(x=0, y=0) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | else: | 
					
						
						|  | return Point(x=x, y=y) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if has_focus and ui_content.cursor_position: | 
					
						
						|  | self._show_key_processor_key_buffer(new_screen) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  | ), | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: str | None | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  | style = parent_style + " " + to_str(self.style) | 
					
						
						|  |  | 
					
						
						|  | new_screen.fill_area(write_position, style=style, after=False) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | data = key_buffer[-1].data | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  | ] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  | ] | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | colorcolumns = self.colorcolumns | 
					
						
						|  | if callable(colorcolumns): | 
					
						
						|  | colorcolumns = colorcolumns() | 
					
						
						|  |  | 
					
						
						|  | for cc in colorcolumns: | 
					
						
						|  | assert isinstance(cc, ColorColumn) | 
					
						
						|  | column = cc.position | 
					
						
						|  |  | 
					
						
						|  | if column < x + width: | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self.horizontal_scroll = 0 | 
					
						
						|  |  | 
					
						
						|  | def get_line_height(lineno: int) -> int: | 
					
						
						|  | return ui_content.get_height_for_line(lineno, width, self.get_line_prefix) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if width <= 0: | 
					
						
						|  | self.vertical_scroll = ui_content.cursor_position.y | 
					
						
						|  | self.vertical_scroll_2 = 0 | 
					
						
						|  | return | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | line_height = get_line_height(ui_content.cursor_position.y) | 
					
						
						|  | if line_height > height - scroll_offsets_top: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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, | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | self.vertical_scroll = ui_content.cursor_position.y | 
					
						
						|  | self.vertical_scroll_2 = min( | 
					
						
						|  | text_before_height - 1, | 
					
						
						|  | line_height | 
					
						
						|  | - height, | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | def get_min_vertical_scroll() -> int: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | topmost_visible = get_topmost_visible() | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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()) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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." | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if current_scroll < 0: | 
					
						
						|  | current_scroll = 0 | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if ( | 
					
						
						|  | not self.allow_scroll_beyond_bottom() | 
					
						
						|  | and current_scroll > content_size - window_size | 
					
						
						|  | ): | 
					
						
						|  | current_scroll = max(0, content_size - window_size) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if current_scroll > cursor_pos - scroll_offset_start: | 
					
						
						|  | current_scroll = max(0, cursor_pos - scroll_offset_start) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end: | 
					
						
						|  | current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end | 
					
						
						|  |  | 
					
						
						|  | return current_scroll | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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, | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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: | 
					
						
						|  |  | 
					
						
						|  | 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) -> KeyBindingsBase | None: | 
					
						
						|  | 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: int | None, | 
					
						
						|  | ) -> 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: int | None, | 
					
						
						|  | ) -> 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) -> KeyBindingsBase | None: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | return None | 
					
						
						|  |  | 
					
						
						|  | def get_children(self) -> list[Container]: | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | 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 | 
					
						
						|  |  |