1""" 
    2Container for the layout. 
    3(Containers can contain other containers or user interface controls.) 
    4""" 
    5 
    6from __future__ import annotations 
    7 
    8from abc import ABCMeta, abstractmethod 
    9from enum import Enum 
    10from functools import partial 
    11from typing import TYPE_CHECKING, Callable, Sequence, Union, cast 
    12 
    13from prompt_toolkit.application.current import get_app 
    14from prompt_toolkit.cache import SimpleCache 
    15from prompt_toolkit.data_structures import Point 
    16from prompt_toolkit.filters import ( 
    17    FilterOrBool, 
    18    emacs_insert_mode, 
    19    to_filter, 
    20    vi_insert_mode, 
    21) 
    22from prompt_toolkit.formatted_text import ( 
    23    AnyFormattedText, 
    24    StyleAndTextTuples, 
    25    to_formatted_text, 
    26) 
    27from prompt_toolkit.formatted_text.utils import ( 
    28    fragment_list_to_text, 
    29    fragment_list_width, 
    30) 
    31from prompt_toolkit.key_binding import KeyBindingsBase 
    32from prompt_toolkit.mouse_events import MouseEvent, MouseEventType 
    33from prompt_toolkit.utils import get_cwidth, take_using_weights, to_int, to_str 
    34 
    35from .controls import ( 
    36    DummyControl, 
    37    FormattedTextControl, 
    38    GetLinePrefixCallable, 
    39    UIContent, 
    40    UIControl, 
    41) 
    42from .dimension import ( 
    43    AnyDimension, 
    44    Dimension, 
    45    max_layout_dimensions, 
    46    sum_layout_dimensions, 
    47    to_dimension, 
    48) 
    49from .margins import Margin 
    50from .mouse_handlers import MouseHandlers 
    51from .screen import _CHAR_CACHE, Screen, WritePosition 
    52from .utils import explode_text_fragments 
    53 
    54if TYPE_CHECKING: 
    55    from typing_extensions import Protocol, TypeGuard 
    56 
    57    from prompt_toolkit.key_binding.key_bindings import NotImplementedOrNone 
    58 
    59 
    60__all__ = [ 
    61    "AnyContainer", 
    62    "Container", 
    63    "HorizontalAlign", 
    64    "VerticalAlign", 
    65    "HSplit", 
    66    "VSplit", 
    67    "FloatContainer", 
    68    "Float", 
    69    "WindowAlign", 
    70    "Window", 
    71    "WindowRenderInfo", 
    72    "ConditionalContainer", 
    73    "ScrollOffsets", 
    74    "ColorColumn", 
    75    "to_container", 
    76    "to_window", 
    77    "is_container", 
    78    "DynamicContainer", 
    79] 
    80 
    81 
    82class Container(metaclass=ABCMeta): 
    83    """ 
    84    Base class for user interface layout. 
    85    """ 
    86 
    87    @abstractmethod 
    88    def reset(self) -> None: 
    89        """ 
    90        Reset the state of this container and all the children. 
    91        (E.g. reset scroll offsets, etc...) 
    92        """ 
    93 
    94    @abstractmethod 
    95    def preferred_width(self, max_available_width: int) -> Dimension: 
    96        """ 
    97        Return a :class:`~prompt_toolkit.layout.Dimension` that represents the 
    98        desired width for this container. 
    99        """ 
    100 
    101    @abstractmethod 
    102    def preferred_height(self, width: int, max_available_height: int) -> Dimension: 
    103        """ 
    104        Return a :class:`~prompt_toolkit.layout.Dimension` that represents the 
    105        desired height for this container. 
    106        """ 
    107 
    108    @abstractmethod 
    109    def write_to_screen( 
    110        self, 
    111        screen: Screen, 
    112        mouse_handlers: MouseHandlers, 
    113        write_position: WritePosition, 
    114        parent_style: str, 
    115        erase_bg: bool, 
    116        z_index: int | None, 
    117    ) -> None: 
    118        """ 
    119        Write the actual content to the screen. 
    120 
    121        :param screen: :class:`~prompt_toolkit.layout.screen.Screen` 
    122        :param mouse_handlers: :class:`~prompt_toolkit.layout.mouse_handlers.MouseHandlers`. 
    123        :param parent_style: Style string to pass to the :class:`.Window` 
    124            object. This will be applied to all content of the windows. 
    125            :class:`.VSplit` and :class:`.HSplit` can use it to pass their 
    126            style down to the windows that they contain. 
    127        :param z_index: Used for propagating z_index from parent to child. 
    128        """ 
    129 
    130    def is_modal(self) -> bool: 
    131        """ 
    132        When this container is modal, key bindings from parent containers are 
    133        not taken into account if a user control in this container is focused. 
    134        """ 
    135        return False 
    136 
    137    def get_key_bindings(self) -> KeyBindingsBase | None: 
    138        """ 
    139        Returns a :class:`.KeyBindings` object. These bindings become active when any 
    140        user control in this container has the focus, except if any containers 
    141        between this container and the focused user control is modal. 
    142        """ 
    143        return None 
    144 
    145    @abstractmethod 
    146    def get_children(self) -> list[Container]: 
    147        """ 
    148        Return the list of child :class:`.Container` objects. 
    149        """ 
    150        return [] 
    151 
    152 
    153if TYPE_CHECKING: 
    154 
    155    class MagicContainer(Protocol): 
    156        """ 
    157        Any object that implements ``__pt_container__`` represents a container. 
    158        """ 
    159 
    160        def __pt_container__(self) -> AnyContainer: ... 
    161 
    162 
    163AnyContainer = Union[Container, "MagicContainer"] 
    164 
    165 
    166def _window_too_small() -> Window: 
    167    "Create a `Window` that displays the 'Window too small' text." 
    168    return Window( 
    169        FormattedTextControl(text=[("class:window-too-small", " Window too small... ")]) 
    170    ) 
    171 
    172 
    173class VerticalAlign(Enum): 
    174    "Alignment for `HSplit`." 
    175 
    176    TOP = "TOP" 
    177    CENTER = "CENTER" 
    178    BOTTOM = "BOTTOM" 
    179    JUSTIFY = "JUSTIFY" 
    180 
    181 
    182class HorizontalAlign(Enum): 
    183    "Alignment for `VSplit`." 
    184 
    185    LEFT = "LEFT" 
    186    CENTER = "CENTER" 
    187    RIGHT = "RIGHT" 
    188    JUSTIFY = "JUSTIFY" 
    189 
    190 
    191class _Split(Container): 
    192    """ 
    193    The common parts of `VSplit` and `HSplit`. 
    194    """ 
    195 
    196    def __init__( 
    197        self, 
    198        children: Sequence[AnyContainer], 
    199        window_too_small: Container | None = None, 
    200        padding: AnyDimension = Dimension.exact(0), 
    201        padding_char: str | None = None, 
    202        padding_style: str = "", 
    203        width: AnyDimension = None, 
    204        height: AnyDimension = None, 
    205        z_index: int | None = None, 
    206        modal: bool = False, 
    207        key_bindings: KeyBindingsBase | None = None, 
    208        style: str | Callable[[], str] = "", 
    209    ) -> None: 
    210        self.children = [to_container(c) for c in children] 
    211        self.window_too_small = window_too_small or _window_too_small() 
    212        self.padding = padding 
    213        self.padding_char = padding_char 
    214        self.padding_style = padding_style 
    215 
    216        self.width = width 
    217        self.height = height 
    218        self.z_index = z_index 
    219 
    220        self.modal = modal 
    221        self.key_bindings = key_bindings 
    222        self.style = style 
    223 
    224    def is_modal(self) -> bool: 
    225        return self.modal 
    226 
    227    def get_key_bindings(self) -> KeyBindingsBase | None: 
    228        return self.key_bindings 
    229 
    230    def get_children(self) -> list[Container]: 
    231        return self.children 
    232 
    233 
    234class HSplit(_Split): 
    235    """ 
    236    Several layouts, one stacked above/under the other. :: 
    237 
    238        +--------------------+ 
    239        |                    | 
    240        +--------------------+ 
    241        |                    | 
    242        +--------------------+ 
    243 
    244    By default, this doesn't display a horizontal line between the children, 
    245    but if this is something you need, then create a HSplit as follows:: 
    246 
    247        HSplit(children=[ ... ], padding_char='-', 
    248               padding=1, padding_style='#ffff00') 
    249 
    250    :param children: List of child :class:`.Container` objects. 
    251    :param window_too_small: A :class:`.Container` object that is displayed if 
    252        there is not enough space for all the children. By default, this is a 
    253        "Window too small" message. 
    254    :param align: `VerticalAlign` value. 
    255    :param width: When given, use this width instead of looking at the children. 
    256    :param height: When given, use this height instead of looking at the children. 
    257    :param z_index: (int or None) When specified, this can be used to bring 
    258        element in front of floating elements.  `None` means: inherit from parent. 
    259    :param style: A style string. 
    260    :param modal: ``True`` or ``False``. 
    261    :param key_bindings: ``None`` or a :class:`.KeyBindings` object. 
    262 
    263    :param padding: (`Dimension` or int), size to be used for the padding. 
    264    :param padding_char: Character to be used for filling in the padding. 
    265    :param padding_style: Style to applied to the padding. 
    266    """ 
    267 
    268    def __init__( 
    269        self, 
    270        children: Sequence[AnyContainer], 
    271        window_too_small: Container | None = None, 
    272        align: VerticalAlign = VerticalAlign.JUSTIFY, 
    273        padding: AnyDimension = 0, 
    274        padding_char: str | None = None, 
    275        padding_style: str = "", 
    276        width: AnyDimension = None, 
    277        height: AnyDimension = None, 
    278        z_index: int | None = None, 
    279        modal: bool = False, 
    280        key_bindings: KeyBindingsBase | None = None, 
    281        style: str | Callable[[], str] = "", 
    282    ) -> None: 
    283        super().__init__( 
    284            children=children, 
    285            window_too_small=window_too_small, 
    286            padding=padding, 
    287            padding_char=padding_char, 
    288            padding_style=padding_style, 
    289            width=width, 
    290            height=height, 
    291            z_index=z_index, 
    292            modal=modal, 
    293            key_bindings=key_bindings, 
    294            style=style, 
    295        ) 
    296 
    297        self.align = align 
    298 
    299        self._children_cache: SimpleCache[tuple[Container, ...], list[Container]] = ( 
    300            SimpleCache(maxsize=1) 
    301        ) 
    302        self._remaining_space_window = Window()  # Dummy window. 
    303 
    304    def preferred_width(self, max_available_width: int) -> Dimension: 
    305        if self.width is not None: 
    306            return to_dimension(self.width) 
    307 
    308        if self.children: 
    309            dimensions = [c.preferred_width(max_available_width) for c in self.children] 
    310            return max_layout_dimensions(dimensions) 
    311        else: 
    312            return Dimension() 
    313 
    314    def preferred_height(self, width: int, max_available_height: int) -> Dimension: 
    315        if self.height is not None: 
    316            return to_dimension(self.height) 
    317 
    318        dimensions = [ 
    319            c.preferred_height(width, max_available_height) for c in self._all_children 
    320        ] 
    321        return sum_layout_dimensions(dimensions) 
    322 
    323    def reset(self) -> None: 
    324        for c in self.children: 
    325            c.reset() 
    326 
    327    @property 
    328    def _all_children(self) -> list[Container]: 
    329        """ 
    330        List of child objects, including padding. 
    331        """ 
    332 
    333        def get() -> list[Container]: 
    334            result: list[Container] = [] 
    335 
    336            # Padding Top. 
    337            if self.align in (VerticalAlign.CENTER, VerticalAlign.BOTTOM): 
    338                result.append(Window(width=Dimension(preferred=0))) 
    339 
    340            # The children with padding. 
    341            for child in self.children: 
    342                result.append(child) 
    343                result.append( 
    344                    Window( 
    345                        height=self.padding, 
    346                        char=self.padding_char, 
    347                        style=self.padding_style, 
    348                    ) 
    349                ) 
    350            if result: 
    351                result.pop() 
    352 
    353            # Padding right. 
    354            if self.align in (VerticalAlign.CENTER, VerticalAlign.TOP): 
    355                result.append(Window(width=Dimension(preferred=0))) 
    356 
    357            return result 
    358 
    359        return self._children_cache.get(tuple(self.children), get) 
    360 
    361    def write_to_screen( 
    362        self, 
    363        screen: Screen, 
    364        mouse_handlers: MouseHandlers, 
    365        write_position: WritePosition, 
    366        parent_style: str, 
    367        erase_bg: bool, 
    368        z_index: int | None, 
    369    ) -> None: 
    370        """ 
    371        Render the prompt to a `Screen` instance. 
    372 
    373        :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class 
    374            to which the output has to be written. 
    375        """ 
    376        sizes = self._divide_heights(write_position) 
    377        style = parent_style + " " + to_str(self.style) 
    378        z_index = z_index if self.z_index is None else self.z_index 
    379 
    380        if sizes is None: 
    381            self.window_too_small.write_to_screen( 
    382                screen, mouse_handlers, write_position, style, erase_bg, z_index 
    383            ) 
    384        else: 
    385            # 
    386            ypos = write_position.ypos 
    387            xpos = write_position.xpos 
    388            width = write_position.width 
    389 
    390            # Draw child panes. 
    391            for s, c in zip(sizes, self._all_children): 
    392                c.write_to_screen( 
    393                    screen, 
    394                    mouse_handlers, 
    395                    WritePosition(xpos, ypos, width, s), 
    396                    style, 
    397                    erase_bg, 
    398                    z_index, 
    399                ) 
    400                ypos += s 
    401 
    402            # Fill in the remaining space. This happens when a child control 
    403            # refuses to take more space and we don't have any padding. Adding a 
    404            # dummy child control for this (in `self._all_children`) is not 
    405            # desired, because in some situations, it would take more space, even 
    406            # when it's not required. This is required to apply the styling. 
    407            remaining_height = write_position.ypos + write_position.height - ypos 
    408            if remaining_height > 0: 
    409                self._remaining_space_window.write_to_screen( 
    410                    screen, 
    411                    mouse_handlers, 
    412                    WritePosition(xpos, ypos, width, remaining_height), 
    413                    style, 
    414                    erase_bg, 
    415                    z_index, 
    416                ) 
    417 
    418    def _divide_heights(self, write_position: WritePosition) -> list[int] | None: 
    419        """ 
    420        Return the heights for all rows. 
    421        Or None when there is not enough space. 
    422        """ 
    423        if not self.children: 
    424            return [] 
    425 
    426        width = write_position.width 
    427        height = write_position.height 
    428 
    429        # Calculate heights. 
    430        dimensions = [c.preferred_height(width, height) for c in self._all_children] 
    431 
    432        # Sum dimensions 
    433        sum_dimensions = sum_layout_dimensions(dimensions) 
    434 
    435        # If there is not enough space for both. 
    436        # Don't do anything. 
    437        if sum_dimensions.min > height: 
    438            return None 
    439 
    440        # Find optimal sizes. (Start with minimal size, increase until we cover 
    441        # the whole height.) 
    442        sizes = [d.min for d in dimensions] 
    443 
    444        child_generator = take_using_weights( 
    445            items=list(range(len(dimensions))), weights=[d.weight for d in dimensions] 
    446        ) 
    447 
    448        i = next(child_generator) 
    449 
    450        # Increase until we meet at least the 'preferred' size. 
    451        preferred_stop = min(height, sum_dimensions.preferred) 
    452        preferred_dimensions = [d.preferred for d in dimensions] 
    453 
    454        while sum(sizes) < preferred_stop: 
    455            if sizes[i] < preferred_dimensions[i]: 
    456                sizes[i] += 1 
    457            i = next(child_generator) 
    458 
    459        # Increase until we use all the available space. (or until "max") 
    460        if not get_app().is_done: 
    461            max_stop = min(height, sum_dimensions.max) 
    462            max_dimensions = [d.max for d in dimensions] 
    463 
    464            while sum(sizes) < max_stop: 
    465                if sizes[i] < max_dimensions[i]: 
    466                    sizes[i] += 1 
    467                i = next(child_generator) 
    468 
    469        return sizes 
    470 
    471 
    472class VSplit(_Split): 
    473    """ 
    474    Several layouts, one stacked left/right of the other. :: 
    475 
    476        +---------+----------+ 
    477        |         |          | 
    478        |         |          | 
    479        +---------+----------+ 
    480 
    481    By default, this doesn't display a vertical line between the children, but 
    482    if this is something you need, then create a HSplit as follows:: 
    483 
    484        VSplit(children=[ ... ], padding_char='|', 
    485               padding=1, padding_style='#ffff00') 
    486 
    487    :param children: List of child :class:`.Container` objects. 
    488    :param window_too_small: A :class:`.Container` object that is displayed if 
    489        there is not enough space for all the children. By default, this is a 
    490        "Window too small" message. 
    491    :param align: `HorizontalAlign` value. 
    492    :param width: When given, use this width instead of looking at the children. 
    493    :param height: When given, use this height instead of looking at the children. 
    494    :param z_index: (int or None) When specified, this can be used to bring 
    495        element in front of floating elements.  `None` means: inherit from parent. 
    496    :param style: A style string. 
    497    :param modal: ``True`` or ``False``. 
    498    :param key_bindings: ``None`` or a :class:`.KeyBindings` object. 
    499 
    500    :param padding: (`Dimension` or int), size to be used for the padding. 
    501    :param padding_char: Character to be used for filling in the padding. 
    502    :param padding_style: Style to applied to the padding. 
    503    """ 
    504 
    505    def __init__( 
    506        self, 
    507        children: Sequence[AnyContainer], 
    508        window_too_small: Container | None = None, 
    509        align: HorizontalAlign = HorizontalAlign.JUSTIFY, 
    510        padding: AnyDimension = 0, 
    511        padding_char: str | None = None, 
    512        padding_style: str = "", 
    513        width: AnyDimension = None, 
    514        height: AnyDimension = None, 
    515        z_index: int | None = None, 
    516        modal: bool = False, 
    517        key_bindings: KeyBindingsBase | None = None, 
    518        style: str | Callable[[], str] = "", 
    519    ) -> None: 
    520        super().__init__( 
    521            children=children, 
    522            window_too_small=window_too_small, 
    523            padding=padding, 
    524            padding_char=padding_char, 
    525            padding_style=padding_style, 
    526            width=width, 
    527            height=height, 
    528            z_index=z_index, 
    529            modal=modal, 
    530            key_bindings=key_bindings, 
    531            style=style, 
    532        ) 
    533 
    534        self.align = align 
    535 
    536        self._children_cache: SimpleCache[tuple[Container, ...], list[Container]] = ( 
    537            SimpleCache(maxsize=1) 
    538        ) 
    539        self._remaining_space_window = Window()  # Dummy window. 
    540 
    541    def preferred_width(self, max_available_width: int) -> Dimension: 
    542        if self.width is not None: 
    543            return to_dimension(self.width) 
    544 
    545        dimensions = [ 
    546            c.preferred_width(max_available_width) for c in self._all_children 
    547        ] 
    548 
    549        return sum_layout_dimensions(dimensions) 
    550 
    551    def preferred_height(self, width: int, max_available_height: int) -> Dimension: 
    552        if self.height is not None: 
    553            return to_dimension(self.height) 
    554 
    555        # At the point where we want to calculate the heights, the widths have 
    556        # already been decided. So we can trust `width` to be the actual 
    557        # `width` that's going to be used for the rendering. So, 
    558        # `divide_widths` is supposed to use all of the available width. 
    559        # Using only the `preferred` width caused a bug where the reported 
    560        # height was more than required. (we had a `BufferControl` which did 
    561        # wrap lines because of the smaller width returned by `_divide_widths`. 
    562 
    563        sizes = self._divide_widths(width) 
    564        children = self._all_children 
    565 
    566        if sizes is None: 
    567            return Dimension() 
    568        else: 
    569            dimensions = [ 
    570                c.preferred_height(s, max_available_height) 
    571                for s, c in zip(sizes, children) 
    572            ] 
    573            return max_layout_dimensions(dimensions) 
    574 
    575    def reset(self) -> None: 
    576        for c in self.children: 
    577            c.reset() 
    578 
    579    @property 
    580    def _all_children(self) -> list[Container]: 
    581        """ 
    582        List of child objects, including padding. 
    583        """ 
    584 
    585        def get() -> list[Container]: 
    586            result: list[Container] = [] 
    587 
    588            # Padding left. 
    589            if self.align in (HorizontalAlign.CENTER, HorizontalAlign.RIGHT): 
    590                result.append(Window(width=Dimension(preferred=0))) 
    591 
    592            # The children with padding. 
    593            for child in self.children: 
    594                result.append(child) 
    595                result.append( 
    596                    Window( 
    597                        width=self.padding, 
    598                        char=self.padding_char, 
    599                        style=self.padding_style, 
    600                    ) 
    601                ) 
    602            if result: 
    603                result.pop() 
    604 
    605            # Padding right. 
    606            if self.align in (HorizontalAlign.CENTER, HorizontalAlign.LEFT): 
    607                result.append(Window(width=Dimension(preferred=0))) 
    608 
    609            return result 
    610 
    611        return self._children_cache.get(tuple(self.children), get) 
    612 
    613    def _divide_widths(self, width: int) -> list[int] | None: 
    614        """ 
    615        Return the widths for all columns. 
    616        Or None when there is not enough space. 
    617        """ 
    618        children = self._all_children 
    619 
    620        if not children: 
    621            return [] 
    622 
    623        # Calculate widths. 
    624        dimensions = [c.preferred_width(width) for c in children] 
    625        preferred_dimensions = [d.preferred for d in dimensions] 
    626 
    627        # Sum dimensions 
    628        sum_dimensions = sum_layout_dimensions(dimensions) 
    629 
    630        # If there is not enough space for both. 
    631        # Don't do anything. 
    632        if sum_dimensions.min > width: 
    633            return None 
    634 
    635        # Find optimal sizes. (Start with minimal size, increase until we cover 
    636        # the whole width.) 
    637        sizes = [d.min for d in dimensions] 
    638 
    639        child_generator = take_using_weights( 
    640            items=list(range(len(dimensions))), weights=[d.weight for d in dimensions] 
    641        ) 
    642 
    643        i = next(child_generator) 
    644 
    645        # Increase until we meet at least the 'preferred' size. 
    646        preferred_stop = min(width, sum_dimensions.preferred) 
    647 
    648        while sum(sizes) < preferred_stop: 
    649            if sizes[i] < preferred_dimensions[i]: 
    650                sizes[i] += 1 
    651            i = next(child_generator) 
    652 
    653        # Increase until we use all the available space. 
    654        max_dimensions = [d.max for d in dimensions] 
    655        max_stop = min(width, sum_dimensions.max) 
    656 
    657        while sum(sizes) < max_stop: 
    658            if sizes[i] < max_dimensions[i]: 
    659                sizes[i] += 1 
    660            i = next(child_generator) 
    661 
    662        return sizes 
    663 
    664    def write_to_screen( 
    665        self, 
    666        screen: Screen, 
    667        mouse_handlers: MouseHandlers, 
    668        write_position: WritePosition, 
    669        parent_style: str, 
    670        erase_bg: bool, 
    671        z_index: int | None, 
    672    ) -> None: 
    673        """ 
    674        Render the prompt to a `Screen` instance. 
    675 
    676        :param screen: The :class:`~prompt_toolkit.layout.screen.Screen` class 
    677            to which the output has to be written. 
    678        """ 
    679        if not self.children: 
    680            return 
    681 
    682        children = self._all_children 
    683        sizes = self._divide_widths(write_position.width) 
    684        style = parent_style + " " + to_str(self.style) 
    685        z_index = z_index if self.z_index is None else self.z_index 
    686 
    687        # If there is not enough space. 
    688        if sizes is None: 
    689            self.window_too_small.write_to_screen( 
    690                screen, mouse_handlers, write_position, style, erase_bg, z_index 
    691            ) 
    692            return 
    693 
    694        # Calculate heights, take the largest possible, but not larger than 
    695        # write_position.height. 
    696        heights = [ 
    697            child.preferred_height(width, write_position.height).preferred 
    698            for width, child in zip(sizes, children) 
    699        ] 
    700        height = max(write_position.height, min(write_position.height, max(heights))) 
    701 
    702        # 
    703        ypos = write_position.ypos 
    704        xpos = write_position.xpos 
    705 
    706        # Draw all child panes. 
    707        for s, c in zip(sizes, children): 
    708            c.write_to_screen( 
    709                screen, 
    710                mouse_handlers, 
    711                WritePosition(xpos, ypos, s, height), 
    712                style, 
    713                erase_bg, 
    714                z_index, 
    715            ) 
    716            xpos += s 
    717 
    718        # Fill in the remaining space. This happens when a child control 
    719        # refuses to take more space and we don't have any padding. Adding a 
    720        # dummy child control for this (in `self._all_children`) is not 
    721        # desired, because in some situations, it would take more space, even 
    722        # when it's not required. This is required to apply the styling. 
    723        remaining_width = write_position.xpos + write_position.width - xpos 
    724        if remaining_width > 0: 
    725            self._remaining_space_window.write_to_screen( 
    726                screen, 
    727                mouse_handlers, 
    728                WritePosition(xpos, ypos, remaining_width, height), 
    729                style, 
    730                erase_bg, 
    731                z_index, 
    732            ) 
    733 
    734 
    735class FloatContainer(Container): 
    736    """ 
    737    Container which can contain another container for the background, as well 
    738    as a list of floating containers on top of it. 
    739 
    740    Example Usage:: 
    741 
    742        FloatContainer(content=Window(...), 
    743                       floats=[ 
    744                           Float(xcursor=True, 
    745                                ycursor=True, 
    746                                content=CompletionsMenu(...)) 
    747                       ]) 
    748 
    749    :param z_index: (int or None) When specified, this can be used to bring 
    750        element in front of floating elements.  `None` means: inherit from parent. 
    751        This is the z_index for the whole `Float` container as a whole. 
    752    """ 
    753 
    754    def __init__( 
    755        self, 
    756        content: AnyContainer, 
    757        floats: list[Float], 
    758        modal: bool = False, 
    759        key_bindings: KeyBindingsBase | None = None, 
    760        style: str | Callable[[], str] = "", 
    761        z_index: int | None = None, 
    762    ) -> None: 
    763        self.content = to_container(content) 
    764        self.floats = floats 
    765 
    766        self.modal = modal 
    767        self.key_bindings = key_bindings 
    768        self.style = style 
    769        self.z_index = z_index 
    770 
    771    def reset(self) -> None: 
    772        self.content.reset() 
    773 
    774        for f in self.floats: 
    775            f.content.reset() 
    776 
    777    def preferred_width(self, max_available_width: int) -> Dimension: 
    778        return self.content.preferred_width(max_available_width) 
    779 
    780    def preferred_height(self, width: int, max_available_height: int) -> Dimension: 
    781        """ 
    782        Return the preferred height of the float container. 
    783        (We don't care about the height of the floats, they should always fit 
    784        into the dimensions provided by the container.) 
    785        """ 
    786        return self.content.preferred_height(width, max_available_height) 
    787 
    788    def write_to_screen( 
    789        self, 
    790        screen: Screen, 
    791        mouse_handlers: MouseHandlers, 
    792        write_position: WritePosition, 
    793        parent_style: str, 
    794        erase_bg: bool, 
    795        z_index: int | None, 
    796    ) -> None: 
    797        style = parent_style + " " + to_str(self.style) 
    798        z_index = z_index if self.z_index is None else self.z_index 
    799 
    800        self.content.write_to_screen( 
    801            screen, mouse_handlers, write_position, style, erase_bg, z_index 
    802        ) 
    803 
    804        for number, fl in enumerate(self.floats): 
    805            # z_index of a Float is computed by summing the z_index of the 
    806            # container and the `Float`. 
    807            new_z_index = (z_index or 0) + fl.z_index 
    808            style = parent_style + " " + to_str(self.style) 
    809 
    810            # If the float that we have here, is positioned relative to the 
    811            # cursor position, but the Window that specifies the cursor 
    812            # position is not drawn yet, because it's a Float itself, we have 
    813            # to postpone this calculation. (This is a work-around, but good 
    814            # enough for now.) 
    815            postpone = fl.xcursor is not None or fl.ycursor is not None 
    816 
    817            if postpone: 
    818                new_z_index = ( 
    819                    number + 10**8 
    820                )  # Draw as late as possible, but keep the order. 
    821                screen.draw_with_z_index( 
    822                    z_index=new_z_index, 
    823                    draw_func=partial( 
    824                        self._draw_float, 
    825                        fl, 
    826                        screen, 
    827                        mouse_handlers, 
    828                        write_position, 
    829                        style, 
    830                        erase_bg, 
    831                        new_z_index, 
    832                    ), 
    833                ) 
    834            else: 
    835                self._draw_float( 
    836                    fl, 
    837                    screen, 
    838                    mouse_handlers, 
    839                    write_position, 
    840                    style, 
    841                    erase_bg, 
    842                    new_z_index, 
    843                ) 
    844 
    845    def _draw_float( 
    846        self, 
    847        fl: Float, 
    848        screen: Screen, 
    849        mouse_handlers: MouseHandlers, 
    850        write_position: WritePosition, 
    851        style: str, 
    852        erase_bg: bool, 
    853        z_index: int | None, 
    854    ) -> None: 
    855        "Draw a single Float." 
    856        # When a menu_position was given, use this instead of the cursor 
    857        # position. (These cursor positions are absolute, translate again 
    858        # relative to the write_position.) 
    859        # Note: This should be inside the for-loop, because one float could 
    860        #       set the cursor position to be used for the next one. 
    861        cpos = screen.get_menu_position( 
    862            fl.attach_to_window or get_app().layout.current_window 
    863        ) 
    864        cursor_position = Point( 
    865            x=cpos.x - write_position.xpos, y=cpos.y - write_position.ypos 
    866        ) 
    867 
    868        fl_width = fl.get_width() 
    869        fl_height = fl.get_height() 
    870        width: int 
    871        height: int 
    872        xpos: int 
    873        ypos: int 
    874 
    875        # Left & width given. 
    876        if fl.left is not None and fl_width is not None: 
    877            xpos = fl.left 
    878            width = fl_width 
    879        # Left & right given -> calculate width. 
    880        elif fl.left is not None and fl.right is not None: 
    881            xpos = fl.left 
    882            width = write_position.width - fl.left - fl.right 
    883        # Width & right given -> calculate left. 
    884        elif fl_width is not None and fl.right is not None: 
    885            xpos = write_position.width - fl.right - fl_width 
    886            width = fl_width 
    887        # Near x position of cursor. 
    888        elif fl.xcursor: 
    889            if fl_width is None: 
    890                width = fl.content.preferred_width(write_position.width).preferred 
    891                width = min(write_position.width, width) 
    892            else: 
    893                width = fl_width 
    894 
    895            xpos = cursor_position.x 
    896            if xpos + width > write_position.width: 
    897                xpos = max(0, write_position.width - width) 
    898        # Only width given -> center horizontally. 
    899        elif fl_width: 
    900            xpos = int((write_position.width - fl_width) / 2) 
    901            width = fl_width 
    902        # Otherwise, take preferred width from float content. 
    903        else: 
    904            width = fl.content.preferred_width(write_position.width).preferred 
    905 
    906            if fl.left is not None: 
    907                xpos = fl.left 
    908            elif fl.right is not None: 
    909                xpos = max(0, write_position.width - width - fl.right) 
    910            else:  # Center horizontally. 
    911                xpos = max(0, int((write_position.width - width) / 2)) 
    912 
    913            # Trim. 
    914            width = min(width, write_position.width - xpos) 
    915 
    916        # Top & height given. 
    917        if fl.top is not None and fl_height is not None: 
    918            ypos = fl.top 
    919            height = fl_height 
    920        # Top & bottom given -> calculate height. 
    921        elif fl.top is not None and fl.bottom is not None: 
    922            ypos = fl.top 
    923            height = write_position.height - fl.top - fl.bottom 
    924        # Height & bottom given -> calculate top. 
    925        elif fl_height is not None and fl.bottom is not None: 
    926            ypos = write_position.height - fl_height - fl.bottom 
    927            height = fl_height 
    928        # Near cursor. 
    929        elif fl.ycursor: 
    930            ypos = cursor_position.y + (0 if fl.allow_cover_cursor else 1) 
    931 
    932            if fl_height is None: 
    933                height = fl.content.preferred_height( 
    934                    width, write_position.height 
    935                ).preferred 
    936            else: 
    937                height = fl_height 
    938 
    939            # Reduce height if not enough space. (We can use the height 
    940            # when the content requires it.) 
    941            if height > write_position.height - ypos: 
    942                if write_position.height - ypos + 1 >= ypos: 
    943                    # When the space below the cursor is more than 
    944                    # the space above, just reduce the height. 
    945                    height = write_position.height - ypos 
    946                else: 
    947                    # Otherwise, fit the float above the cursor. 
    948                    height = min(height, cursor_position.y) 
    949                    ypos = cursor_position.y - height 
    950 
    951        # Only height given -> center vertically. 
    952        elif fl_height: 
    953            ypos = int((write_position.height - fl_height) / 2) 
    954            height = fl_height 
    955        # Otherwise, take preferred height from content. 
    956        else: 
    957            height = fl.content.preferred_height(width, write_position.height).preferred 
    958 
    959            if fl.top is not None: 
    960                ypos = fl.top 
    961            elif fl.bottom is not None: 
    962                ypos = max(0, write_position.height - height - fl.bottom) 
    963            else:  # Center vertically. 
    964                ypos = max(0, int((write_position.height - height) / 2)) 
    965 
    966            # Trim. 
    967            height = min(height, write_position.height - ypos) 
    968 
    969        # Write float. 
    970        # (xpos and ypos can be negative: a float can be partially visible.) 
    971        if height > 0 and width > 0: 
    972            wp = WritePosition( 
    973                xpos=xpos + write_position.xpos, 
    974                ypos=ypos + write_position.ypos, 
    975                width=width, 
    976                height=height, 
    977            ) 
    978 
    979            if not fl.hide_when_covering_content or self._area_is_empty(screen, wp): 
    980                fl.content.write_to_screen( 
    981                    screen, 
    982                    mouse_handlers, 
    983                    wp, 
    984                    style, 
    985                    erase_bg=not fl.transparent(), 
    986                    z_index=z_index, 
    987                ) 
    988 
    989    def _area_is_empty(self, screen: Screen, write_position: WritePosition) -> bool: 
    990        """ 
    991        Return True when the area below the write position is still empty. 
    992        (For floats that should not hide content underneath.) 
    993        """ 
    994        wp = write_position 
    995 
    996        for y in range(wp.ypos, wp.ypos + wp.height): 
    997            if y in screen.data_buffer: 
    998                row = screen.data_buffer[y] 
    999 
    1000                for x in range(wp.xpos, wp.xpos + wp.width): 
    1001                    c = row[x] 
    1002                    if c.char != " ": 
    1003                        return False 
    1004 
    1005        return True 
    1006 
    1007    def is_modal(self) -> bool: 
    1008        return self.modal 
    1009 
    1010    def get_key_bindings(self) -> KeyBindingsBase | None: 
    1011        return self.key_bindings 
    1012 
    1013    def get_children(self) -> list[Container]: 
    1014        children = [self.content] 
    1015        children.extend(f.content for f in self.floats) 
    1016        return children 
    1017 
    1018 
    1019class Float: 
    1020    """ 
    1021    Float for use in a :class:`.FloatContainer`. 
    1022    Except for the `content` parameter, all other options are optional. 
    1023 
    1024    :param content: :class:`.Container` instance. 
    1025 
    1026    :param width: :class:`.Dimension` or callable which returns a :class:`.Dimension`. 
    1027    :param height: :class:`.Dimension` or callable which returns a :class:`.Dimension`. 
    1028 
    1029    :param left: Distance to the left edge of the :class:`.FloatContainer`. 
    1030    :param right: Distance to the right edge of the :class:`.FloatContainer`. 
    1031    :param top: Distance to the top of the :class:`.FloatContainer`. 
    1032    :param bottom: Distance to the bottom of the :class:`.FloatContainer`. 
    1033 
    1034    :param attach_to_window: Attach to the cursor from this window, instead of 
    1035        the current window. 
    1036    :param hide_when_covering_content: Hide the float when it covers content underneath. 
    1037    :param allow_cover_cursor: When `False`, make sure to display the float 
    1038        below the cursor. Not on top of the indicated position. 
    1039    :param z_index: Z-index position. For a Float, this needs to be at least 
    1040        one. It is relative to the z_index of the parent container. 
    1041    :param transparent: :class:`.Filter` indicating whether this float needs to be 
    1042        drawn transparently. 
    1043    """ 
    1044 
    1045    def __init__( 
    1046        self, 
    1047        content: AnyContainer, 
    1048        top: int | None = None, 
    1049        right: int | None = None, 
    1050        bottom: int | None = None, 
    1051        left: int | None = None, 
    1052        width: int | Callable[[], int] | None = None, 
    1053        height: int | Callable[[], int] | None = None, 
    1054        xcursor: bool = False, 
    1055        ycursor: bool = False, 
    1056        attach_to_window: AnyContainer | None = None, 
    1057        hide_when_covering_content: bool = False, 
    1058        allow_cover_cursor: bool = False, 
    1059        z_index: int = 1, 
    1060        transparent: bool = False, 
    1061    ) -> None: 
    1062        assert z_index >= 1 
    1063 
    1064        self.left = left 
    1065        self.right = right 
    1066        self.top = top 
    1067        self.bottom = bottom 
    1068 
    1069        self.width = width 
    1070        self.height = height 
    1071 
    1072        self.xcursor = xcursor 
    1073        self.ycursor = ycursor 
    1074 
    1075        self.attach_to_window = ( 
    1076            to_window(attach_to_window) if attach_to_window else None 
    1077        ) 
    1078 
    1079        self.content = to_container(content) 
    1080        self.hide_when_covering_content = hide_when_covering_content 
    1081        self.allow_cover_cursor = allow_cover_cursor 
    1082        self.z_index = z_index 
    1083        self.transparent = to_filter(transparent) 
    1084 
    1085    def get_width(self) -> int | None: 
    1086        if callable(self.width): 
    1087            return self.width() 
    1088        return self.width 
    1089 
    1090    def get_height(self) -> int | None: 
    1091        if callable(self.height): 
    1092            return self.height() 
    1093        return self.height 
    1094 
    1095    def __repr__(self) -> str: 
    1096        return f"Float(content={self.content!r})" 
    1097 
    1098 
    1099class WindowRenderInfo: 
    1100    """ 
    1101    Render information for the last render time of this control. 
    1102    It stores mapping information between the input buffers (in case of a 
    1103    :class:`~prompt_toolkit.layout.controls.BufferControl`) and the actual 
    1104    render position on the output screen. 
    1105 
    1106    (Could be used for implementation of the Vi 'H' and 'L' key bindings as 
    1107    well as implementing mouse support.) 
    1108 
    1109    :param ui_content: The original :class:`.UIContent` instance that contains 
    1110        the whole input, without clipping. (ui_content) 
    1111    :param horizontal_scroll: The horizontal scroll of the :class:`.Window` instance. 
    1112    :param vertical_scroll: The vertical scroll of the :class:`.Window` instance. 
    1113    :param window_width: The width of the window that displays the content, 
    1114        without the margins. 
    1115    :param window_height: The height of the window that displays the content. 
    1116    :param configured_scroll_offsets: The scroll offsets as configured for the 
    1117        :class:`Window` instance. 
    1118    :param visible_line_to_row_col: Mapping that maps the row numbers on the 
    1119        displayed screen (starting from zero for the first visible line) to 
    1120        (row, col) tuples pointing to the row and column of the :class:`.UIContent`. 
    1121    :param rowcol_to_yx: Mapping that maps (row, column) tuples representing 
    1122        coordinates of the :class:`UIContent` to (y, x) absolute coordinates at 
    1123        the rendered screen. 
    1124    """ 
    1125 
    1126    def __init__( 
    1127        self, 
    1128        window: Window, 
    1129        ui_content: UIContent, 
    1130        horizontal_scroll: int, 
    1131        vertical_scroll: int, 
    1132        window_width: int, 
    1133        window_height: int, 
    1134        configured_scroll_offsets: ScrollOffsets, 
    1135        visible_line_to_row_col: dict[int, tuple[int, int]], 
    1136        rowcol_to_yx: dict[tuple[int, int], tuple[int, int]], 
    1137        x_offset: int, 
    1138        y_offset: int, 
    1139        wrap_lines: bool, 
    1140    ) -> None: 
    1141        self.window = window 
    1142        self.ui_content = ui_content 
    1143        self.vertical_scroll = vertical_scroll 
    1144        self.window_width = window_width  # Width without margins. 
    1145        self.window_height = window_height 
    1146 
    1147        self.configured_scroll_offsets = configured_scroll_offsets 
    1148        self.visible_line_to_row_col = visible_line_to_row_col 
    1149        self.wrap_lines = wrap_lines 
    1150 
    1151        self._rowcol_to_yx = rowcol_to_yx  # row/col from input to absolute y/x 
    1152        # screen coordinates. 
    1153        self._x_offset = x_offset 
    1154        self._y_offset = y_offset 
    1155 
    1156    @property 
    1157    def visible_line_to_input_line(self) -> dict[int, int]: 
    1158        return { 
    1159            visible_line: rowcol[0] 
    1160            for visible_line, rowcol in self.visible_line_to_row_col.items() 
    1161        } 
    1162 
    1163    @property 
    1164    def cursor_position(self) -> Point: 
    1165        """ 
    1166        Return the cursor position coordinates, relative to the left/top corner 
    1167        of the rendered screen. 
    1168        """ 
    1169        cpos = self.ui_content.cursor_position 
    1170        try: 
    1171            y, x = self._rowcol_to_yx[cpos.y, cpos.x] 
    1172        except KeyError: 
    1173            # For `DummyControl` for instance, the content can be empty, and so 
    1174            # will `_rowcol_to_yx` be. Return 0/0 by default. 
    1175            return Point(x=0, y=0) 
    1176        else: 
    1177            return Point(x=x - self._x_offset, y=y - self._y_offset) 
    1178 
    1179    @property 
    1180    def applied_scroll_offsets(self) -> ScrollOffsets: 
    1181        """ 
    1182        Return a :class:`.ScrollOffsets` instance that indicates the actual 
    1183        offset. This can be less than or equal to what's configured. E.g, when 
    1184        the cursor is completely at the top, the top offset will be zero rather 
    1185        than what's configured. 
    1186        """ 
    1187        if self.displayed_lines[0] == 0: 
    1188            top = 0 
    1189        else: 
    1190            # Get row where the cursor is displayed. 
    1191            y = self.input_line_to_visible_line[self.ui_content.cursor_position.y] 
    1192            top = min(y, self.configured_scroll_offsets.top) 
    1193 
    1194        return ScrollOffsets( 
    1195            top=top, 
    1196            bottom=min( 
    1197                self.ui_content.line_count - self.displayed_lines[-1] - 1, 
    1198                self.configured_scroll_offsets.bottom, 
    1199            ), 
    1200            # For left/right, it probably doesn't make sense to return something. 
    1201            # (We would have to calculate the widths of all the lines and keep 
    1202            # double width characters in mind.) 
    1203            left=0, 
    1204            right=0, 
    1205        ) 
    1206 
    1207    @property 
    1208    def displayed_lines(self) -> list[int]: 
    1209        """ 
    1210        List of all the visible rows. (Line numbers of the input buffer.) 
    1211        The last line may not be entirely visible. 
    1212        """ 
    1213        return sorted(row for row, col in self.visible_line_to_row_col.values()) 
    1214 
    1215    @property 
    1216    def input_line_to_visible_line(self) -> dict[int, int]: 
    1217        """ 
    1218        Return the dictionary mapping the line numbers of the input buffer to 
    1219        the lines of the screen. When a line spans several rows at the screen, 
    1220        the first row appears in the dictionary. 
    1221        """ 
    1222        result: dict[int, int] = {} 
    1223        for k, v in self.visible_line_to_input_line.items(): 
    1224            if v in result: 
    1225                result[v] = min(result[v], k) 
    1226            else: 
    1227                result[v] = k 
    1228        return result 
    1229 
    1230    def first_visible_line(self, after_scroll_offset: bool = False) -> int: 
    1231        """ 
    1232        Return the line number (0 based) of the input document that corresponds 
    1233        with the first visible line. 
    1234        """ 
    1235        if after_scroll_offset: 
    1236            return self.displayed_lines[self.applied_scroll_offsets.top] 
    1237        else: 
    1238            return self.displayed_lines[0] 
    1239 
    1240    def last_visible_line(self, before_scroll_offset: bool = False) -> int: 
    1241        """ 
    1242        Like `first_visible_line`, but for the last visible line. 
    1243        """ 
    1244        if before_scroll_offset: 
    1245            return self.displayed_lines[-1 - self.applied_scroll_offsets.bottom] 
    1246        else: 
    1247            return self.displayed_lines[-1] 
    1248 
    1249    def center_visible_line( 
    1250        self, before_scroll_offset: bool = False, after_scroll_offset: bool = False 
    1251    ) -> int: 
    1252        """ 
    1253        Like `first_visible_line`, but for the center visible line. 
    1254        """ 
    1255        return ( 
    1256            self.first_visible_line(after_scroll_offset) 
    1257            + ( 
    1258                self.last_visible_line(before_scroll_offset) 
    1259                - self.first_visible_line(after_scroll_offset) 
    1260            ) 
    1261            // 2 
    1262        ) 
    1263 
    1264    @property 
    1265    def content_height(self) -> int: 
    1266        """ 
    1267        The full height of the user control. 
    1268        """ 
    1269        return self.ui_content.line_count 
    1270 
    1271    @property 
    1272    def full_height_visible(self) -> bool: 
    1273        """ 
    1274        True when the full height is visible (There is no vertical scroll.) 
    1275        """ 
    1276        return ( 
    1277            self.vertical_scroll == 0 
    1278            and self.last_visible_line() == self.content_height 
    1279        ) 
    1280 
    1281    @property 
    1282    def top_visible(self) -> bool: 
    1283        """ 
    1284        True when the top of the buffer is visible. 
    1285        """ 
    1286        return self.vertical_scroll == 0 
    1287 
    1288    @property 
    1289    def bottom_visible(self) -> bool: 
    1290        """ 
    1291        True when the bottom of the buffer is visible. 
    1292        """ 
    1293        return self.last_visible_line() == self.content_height - 1 
    1294 
    1295    @property 
    1296    def vertical_scroll_percentage(self) -> int: 
    1297        """ 
    1298        Vertical scroll as a percentage. (0 means: the top is visible, 
    1299        100 means: the bottom is visible.) 
    1300        """ 
    1301        if self.bottom_visible: 
    1302            return 100 
    1303        else: 
    1304            return 100 * self.vertical_scroll // self.content_height 
    1305 
    1306    def get_height_for_line(self, lineno: int) -> int: 
    1307        """ 
    1308        Return the height of the given line. 
    1309        (The height that it would take, if this line became visible.) 
    1310        """ 
    1311        if self.wrap_lines: 
    1312            return self.ui_content.get_height_for_line( 
    1313                lineno, self.window_width, self.window.get_line_prefix 
    1314            ) 
    1315        else: 
    1316            return 1 
    1317 
    1318 
    1319class ScrollOffsets: 
    1320    """ 
    1321    Scroll offsets for the :class:`.Window` class. 
    1322 
    1323    Note that left/right offsets only make sense if line wrapping is disabled. 
    1324    """ 
    1325 
    1326    def __init__( 
    1327        self, 
    1328        top: int | Callable[[], int] = 0, 
    1329        bottom: int | Callable[[], int] = 0, 
    1330        left: int | Callable[[], int] = 0, 
    1331        right: int | Callable[[], int] = 0, 
    1332    ) -> None: 
    1333        self._top = top 
    1334        self._bottom = bottom 
    1335        self._left = left 
    1336        self._right = right 
    1337 
    1338    @property 
    1339    def top(self) -> int: 
    1340        return to_int(self._top) 
    1341 
    1342    @property 
    1343    def bottom(self) -> int: 
    1344        return to_int(self._bottom) 
    1345 
    1346    @property 
    1347    def left(self) -> int: 
    1348        return to_int(self._left) 
    1349 
    1350    @property 
    1351    def right(self) -> int: 
    1352        return to_int(self._right) 
    1353 
    1354    def __repr__(self) -> str: 
    1355        return f"ScrollOffsets(top={self._top!r}, bottom={self._bottom!r}, left={self._left!r}, right={self._right!r})" 
    1356 
    1357 
    1358class ColorColumn: 
    1359    """ 
    1360    Column for a :class:`.Window` to be colored. 
    1361    """ 
    1362 
    1363    def __init__(self, position: int, style: str = "class:color-column") -> None: 
    1364        self.position = position 
    1365        self.style = style 
    1366 
    1367 
    1368_in_insert_mode = vi_insert_mode | emacs_insert_mode 
    1369 
    1370 
    1371class WindowAlign(Enum): 
    1372    """ 
    1373    Alignment of the Window content. 
    1374 
    1375    Note that this is different from `HorizontalAlign` and `VerticalAlign`, 
    1376    which are used for the alignment of the child containers in respectively 
    1377    `VSplit` and `HSplit`. 
    1378    """ 
    1379 
    1380    LEFT = "LEFT" 
    1381    RIGHT = "RIGHT" 
    1382    CENTER = "CENTER" 
    1383 
    1384 
    1385class Window(Container): 
    1386    """ 
    1387    Container that holds a control. 
    1388 
    1389    :param content: :class:`.UIControl` instance. 
    1390    :param width: :class:`.Dimension` instance or callable. 
    1391    :param height: :class:`.Dimension` instance or callable. 
    1392    :param z_index: When specified, this can be used to bring element in front 
    1393        of floating elements. 
    1394    :param dont_extend_width: When `True`, don't take up more width then the 
    1395                              preferred width reported by the control. 
    1396    :param dont_extend_height: When `True`, don't take up more width then the 
    1397                               preferred height reported by the control. 
    1398    :param ignore_content_width: A `bool` or :class:`.Filter` instance. Ignore 
    1399        the :class:`.UIContent` width when calculating the dimensions. 
    1400    :param ignore_content_height: A `bool` or :class:`.Filter` instance. Ignore 
    1401        the :class:`.UIContent` height when calculating the dimensions. 
    1402    :param left_margins: A list of :class:`.Margin` instance to be displayed on 
    1403        the left. For instance: :class:`~prompt_toolkit.layout.NumberedMargin` 
    1404        can be one of them in order to show line numbers. 
    1405    :param right_margins: Like `left_margins`, but on the other side. 
    1406    :param scroll_offsets: :class:`.ScrollOffsets` instance, representing the 
    1407        preferred amount of lines/columns to be always visible before/after the 
    1408        cursor. When both top and bottom are a very high number, the cursor 
    1409        will be centered vertically most of the time. 
    1410    :param allow_scroll_beyond_bottom: A `bool` or 
    1411        :class:`.Filter` instance. When True, allow scrolling so far, that the 
    1412        top part of the content is not visible anymore, while there is still 
    1413        empty space available at the bottom of the window. In the Vi editor for 
    1414        instance, this is possible. You will see tildes while the top part of 
    1415        the body is hidden. 
    1416    :param wrap_lines: A `bool` or :class:`.Filter` instance. When True, don't 
    1417        scroll horizontally, but wrap lines instead. 
    1418    :param get_vertical_scroll: Callable that takes this window 
    1419        instance as input and returns a preferred vertical scroll. 
    1420        (When this is `None`, the scroll is only determined by the last and 
    1421        current cursor position.) 
    1422    :param get_horizontal_scroll: Callable that takes this window 
    1423        instance as input and returns a preferred vertical scroll. 
    1424    :param always_hide_cursor: A `bool` or 
    1425        :class:`.Filter` instance. When True, never display the cursor, even 
    1426        when the user control specifies a cursor position. 
    1427    :param cursorline: A `bool` or :class:`.Filter` instance. When True, 
    1428        display a cursorline. 
    1429    :param cursorcolumn: A `bool` or :class:`.Filter` instance. When True, 
    1430        display a cursorcolumn. 
    1431    :param colorcolumns: A list of :class:`.ColorColumn` instances that 
    1432        describe the columns to be highlighted, or a callable that returns such 
    1433        a list. 
    1434    :param align: :class:`.WindowAlign` value or callable that returns an 
    1435        :class:`.WindowAlign` value. alignment of content. 
    1436    :param style: A style string. Style to be applied to all the cells in this 
    1437        window. (This can be a callable that returns a string.) 
    1438    :param char: (string) Character to be used for filling the background. This 
    1439        can also be a callable that returns a character. 
    1440    :param get_line_prefix: None or a callable that returns formatted text to 
    1441        be inserted before a line. It takes a line number (int) and a 
    1442        wrap_count and returns formatted text. This can be used for 
    1443        implementation of line continuations, things like Vim "breakindent" and 
    1444        so on. 
    1445    """ 
    1446 
    1447    def __init__( 
    1448        self, 
    1449        content: UIControl | None = None, 
    1450        width: AnyDimension = None, 
    1451        height: AnyDimension = None, 
    1452        z_index: int | None = None, 
    1453        dont_extend_width: FilterOrBool = False, 
    1454        dont_extend_height: FilterOrBool = False, 
    1455        ignore_content_width: FilterOrBool = False, 
    1456        ignore_content_height: FilterOrBool = False, 
    1457        left_margins: Sequence[Margin] | None = None, 
    1458        right_margins: Sequence[Margin] | None = None, 
    1459        scroll_offsets: ScrollOffsets | None = None, 
    1460        allow_scroll_beyond_bottom: FilterOrBool = False, 
    1461        wrap_lines: FilterOrBool = False, 
    1462        get_vertical_scroll: Callable[[Window], int] | None = None, 
    1463        get_horizontal_scroll: Callable[[Window], int] | None = None, 
    1464        always_hide_cursor: FilterOrBool = False, 
    1465        cursorline: FilterOrBool = False, 
    1466        cursorcolumn: FilterOrBool = False, 
    1467        colorcolumns: ( 
    1468            None | list[ColorColumn] | Callable[[], list[ColorColumn]] 
    1469        ) = None, 
    1470        align: WindowAlign | Callable[[], WindowAlign] = WindowAlign.LEFT, 
    1471        style: str | Callable[[], str] = "", 
    1472        char: None | str | Callable[[], str] = None, 
    1473        get_line_prefix: GetLinePrefixCallable | None = None, 
    1474    ) -> None: 
    1475        self.allow_scroll_beyond_bottom = to_filter(allow_scroll_beyond_bottom) 
    1476        self.always_hide_cursor = to_filter(always_hide_cursor) 
    1477        self.wrap_lines = to_filter(wrap_lines) 
    1478        self.cursorline = to_filter(cursorline) 
    1479        self.cursorcolumn = to_filter(cursorcolumn) 
    1480 
    1481        self.content = content or DummyControl() 
    1482        self.dont_extend_width = to_filter(dont_extend_width) 
    1483        self.dont_extend_height = to_filter(dont_extend_height) 
    1484        self.ignore_content_width = to_filter(ignore_content_width) 
    1485        self.ignore_content_height = to_filter(ignore_content_height) 
    1486        self.left_margins = left_margins or [] 
    1487        self.right_margins = right_margins or [] 
    1488        self.scroll_offsets = scroll_offsets or ScrollOffsets() 
    1489        self.get_vertical_scroll = get_vertical_scroll 
    1490        self.get_horizontal_scroll = get_horizontal_scroll 
    1491        self.colorcolumns = colorcolumns or [] 
    1492        self.align = align 
    1493        self.style = style 
    1494        self.char = char 
    1495        self.get_line_prefix = get_line_prefix 
    1496 
    1497        self.width = width 
    1498        self.height = height 
    1499        self.z_index = z_index 
    1500 
    1501        # Cache for the screens generated by the margin. 
    1502        self._ui_content_cache: SimpleCache[tuple[int, int, int], UIContent] = ( 
    1503            SimpleCache(maxsize=8) 
    1504        ) 
    1505        self._margin_width_cache: SimpleCache[tuple[Margin, int], int] = SimpleCache( 
    1506            maxsize=1 
    1507        ) 
    1508 
    1509        self.reset() 
    1510 
    1511    def __repr__(self) -> str: 
    1512        return f"Window(content={self.content!r})" 
    1513 
    1514    def reset(self) -> None: 
    1515        self.content.reset() 
    1516 
    1517        #: Scrolling position of the main content. 
    1518        self.vertical_scroll = 0 
    1519        self.horizontal_scroll = 0 
    1520 
    1521        # Vertical scroll 2: this is the vertical offset that a line is 
    1522        # scrolled if a single line (the one that contains the cursor) consumes 
    1523        # all of the vertical space. 
    1524        self.vertical_scroll_2 = 0 
    1525 
    1526        #: Keep render information (mappings between buffer input and render 
    1527        #: output.) 
    1528        self.render_info: WindowRenderInfo | None = None 
    1529 
    1530    def _get_margin_width(self, margin: Margin) -> int: 
    1531        """ 
    1532        Return the width for this margin. 
    1533        (Calculate only once per render time.) 
    1534        """ 
    1535 
    1536        # Margin.get_width, needs to have a UIContent instance. 
    1537        def get_ui_content() -> UIContent: 
    1538            return self._get_ui_content(width=0, height=0) 
    1539 
    1540        def get_width() -> int: 
    1541            return margin.get_width(get_ui_content) 
    1542 
    1543        key = (margin, get_app().render_counter) 
    1544        return self._margin_width_cache.get(key, get_width) 
    1545 
    1546    def _get_total_margin_width(self) -> int: 
    1547        """ 
    1548        Calculate and return the width of the margin (left + right). 
    1549        """ 
    1550        return sum(self._get_margin_width(m) for m in self.left_margins) + sum( 
    1551            self._get_margin_width(m) for m in self.right_margins 
    1552        ) 
    1553 
    1554    def preferred_width(self, max_available_width: int) -> Dimension: 
    1555        """ 
    1556        Calculate the preferred width for this window. 
    1557        """ 
    1558 
    1559        def preferred_content_width() -> int | None: 
    1560            """Content width: is only calculated if no exact width for the 
    1561            window was given.""" 
    1562            if self.ignore_content_width(): 
    1563                return None 
    1564 
    1565            # Calculate the width of the margin. 
    1566            total_margin_width = self._get_total_margin_width() 
    1567 
    1568            # Window of the content. (Can be `None`.) 
    1569            preferred_width = self.content.preferred_width( 
    1570                max_available_width - total_margin_width 
    1571            ) 
    1572 
    1573            if preferred_width is not None: 
    1574                # Include width of the margins. 
    1575                preferred_width += total_margin_width 
    1576            return preferred_width 
    1577 
    1578        # Merge. 
    1579        return self._merge_dimensions( 
    1580            dimension=to_dimension(self.width), 
    1581            get_preferred=preferred_content_width, 
    1582            dont_extend=self.dont_extend_width(), 
    1583        ) 
    1584 
    1585    def preferred_height(self, width: int, max_available_height: int) -> Dimension: 
    1586        """ 
    1587        Calculate the preferred height for this window. 
    1588        """ 
    1589 
    1590        def preferred_content_height() -> int | None: 
    1591            """Content height: is only calculated if no exact height for the 
    1592            window was given.""" 
    1593            if self.ignore_content_height(): 
    1594                return None 
    1595 
    1596            total_margin_width = self._get_total_margin_width() 
    1597            wrap_lines = self.wrap_lines() 
    1598 
    1599            return self.content.preferred_height( 
    1600                width - total_margin_width, 
    1601                max_available_height, 
    1602                wrap_lines, 
    1603                self.get_line_prefix, 
    1604            ) 
    1605 
    1606        return self._merge_dimensions( 
    1607            dimension=to_dimension(self.height), 
    1608            get_preferred=preferred_content_height, 
    1609            dont_extend=self.dont_extend_height(), 
    1610        ) 
    1611 
    1612    @staticmethod 
    1613    def _merge_dimensions( 
    1614        dimension: Dimension | None, 
    1615        get_preferred: Callable[[], int | None], 
    1616        dont_extend: bool = False, 
    1617    ) -> Dimension: 
    1618        """ 
    1619        Take the Dimension from this `Window` class and the received preferred 
    1620        size from the `UIControl` and return a `Dimension` to report to the 
    1621        parent container. 
    1622        """ 
    1623        dimension = dimension or Dimension() 
    1624 
    1625        # When a preferred dimension was explicitly given to the Window, 
    1626        # ignore the UIControl. 
    1627        preferred: int | None 
    1628 
    1629        if dimension.preferred_specified: 
    1630            preferred = dimension.preferred 
    1631        else: 
    1632            # Otherwise, calculate the preferred dimension from the UI control 
    1633            # content. 
    1634            preferred = get_preferred() 
    1635 
    1636        # When a 'preferred' dimension is given by the UIControl, make sure 
    1637        # that it stays within the bounds of the Window. 
    1638        if preferred is not None: 
    1639            if dimension.max_specified: 
    1640                preferred = min(preferred, dimension.max) 
    1641 
    1642            if dimension.min_specified: 
    1643                preferred = max(preferred, dimension.min) 
    1644 
    1645        # When a `dont_extend` flag has been given, use the preferred dimension 
    1646        # also as the max dimension. 
    1647        max_: int | None 
    1648        min_: int | None 
    1649 
    1650        if dont_extend and preferred is not None: 
    1651            max_ = min(dimension.max, preferred) 
    1652        else: 
    1653            max_ = dimension.max if dimension.max_specified else None 
    1654 
    1655        min_ = dimension.min if dimension.min_specified else None 
    1656 
    1657        return Dimension( 
    1658            min=min_, max=max_, preferred=preferred, weight=dimension.weight 
    1659        ) 
    1660 
    1661    def _get_ui_content(self, width: int, height: int) -> UIContent: 
    1662        """ 
    1663        Create a `UIContent` instance. 
    1664        """ 
    1665 
    1666        def get_content() -> UIContent: 
    1667            return self.content.create_content(width=width, height=height) 
    1668 
    1669        key = (get_app().render_counter, width, height) 
    1670        return self._ui_content_cache.get(key, get_content) 
    1671 
    1672    def _get_digraph_char(self) -> str | None: 
    1673        "Return `False`, or the Digraph symbol to be used." 
    1674        app = get_app() 
    1675        if app.quoted_insert: 
    1676            return "^" 
    1677        if app.vi_state.waiting_for_digraph: 
    1678            if app.vi_state.digraph_symbol1: 
    1679                return app.vi_state.digraph_symbol1 
    1680            return "?" 
    1681        return None 
    1682 
    1683    def write_to_screen( 
    1684        self, 
    1685        screen: Screen, 
    1686        mouse_handlers: MouseHandlers, 
    1687        write_position: WritePosition, 
    1688        parent_style: str, 
    1689        erase_bg: bool, 
    1690        z_index: int | None, 
    1691    ) -> None: 
    1692        """ 
    1693        Write window to screen. This renders the user control, the margins and 
    1694        copies everything over to the absolute position at the given screen. 
    1695        """ 
    1696        # If dont_extend_width/height was given. Then reduce width/height in 
    1697        # WritePosition if the parent wanted us to paint in a bigger area. 
    1698        # (This happens if this window is bundled with another window in a 
    1699        # HSplit/VSplit, but with different size requirements.) 
    1700        write_position = WritePosition( 
    1701            xpos=write_position.xpos, 
    1702            ypos=write_position.ypos, 
    1703            width=write_position.width, 
    1704            height=write_position.height, 
    1705        ) 
    1706 
    1707        if self.dont_extend_width(): 
    1708            write_position.width = min( 
    1709                write_position.width, 
    1710                self.preferred_width(write_position.width).preferred, 
    1711            ) 
    1712 
    1713        if self.dont_extend_height(): 
    1714            write_position.height = min( 
    1715                write_position.height, 
    1716                self.preferred_height( 
    1717                    write_position.width, write_position.height 
    1718                ).preferred, 
    1719            ) 
    1720 
    1721        # Draw 
    1722        z_index = z_index if self.z_index is None else self.z_index 
    1723 
    1724        draw_func = partial( 
    1725            self._write_to_screen_at_index, 
    1726            screen, 
    1727            mouse_handlers, 
    1728            write_position, 
    1729            parent_style, 
    1730            erase_bg, 
    1731        ) 
    1732 
    1733        if z_index is None or z_index <= 0: 
    1734            # When no z_index is given, draw right away. 
    1735            draw_func() 
    1736        else: 
    1737            # Otherwise, postpone. 
    1738            screen.draw_with_z_index(z_index=z_index, draw_func=draw_func) 
    1739 
    1740    def _write_to_screen_at_index( 
    1741        self, 
    1742        screen: Screen, 
    1743        mouse_handlers: MouseHandlers, 
    1744        write_position: WritePosition, 
    1745        parent_style: str, 
    1746        erase_bg: bool, 
    1747    ) -> None: 
    1748        # Don't bother writing invisible windows. 
    1749        # (We save some time, but also avoid applying last-line styling.) 
    1750        if write_position.height <= 0 or write_position.width <= 0: 
    1751            return 
    1752 
    1753        # Calculate margin sizes. 
    1754        left_margin_widths = [self._get_margin_width(m) for m in self.left_margins] 
    1755        right_margin_widths = [self._get_margin_width(m) for m in self.right_margins] 
    1756        total_margin_width = sum(left_margin_widths + right_margin_widths) 
    1757 
    1758        # Render UserControl. 
    1759        ui_content = self.content.create_content( 
    1760            write_position.width - total_margin_width, write_position.height 
    1761        ) 
    1762        assert isinstance(ui_content, UIContent) 
    1763 
    1764        # Scroll content. 
    1765        wrap_lines = self.wrap_lines() 
    1766        self._scroll( 
    1767            ui_content, write_position.width - total_margin_width, write_position.height 
    1768        ) 
    1769 
    1770        # Erase background and fill with `char`. 
    1771        self._fill_bg(screen, write_position, erase_bg) 
    1772 
    1773        # Resolve `align` attribute. 
    1774        align = self.align() if callable(self.align) else self.align 
    1775 
    1776        # Write body 
    1777        visible_line_to_row_col, rowcol_to_yx = self._copy_body( 
    1778            ui_content, 
    1779            screen, 
    1780            write_position, 
    1781            sum(left_margin_widths), 
    1782            write_position.width - total_margin_width, 
    1783            self.vertical_scroll, 
    1784            self.horizontal_scroll, 
    1785            wrap_lines=wrap_lines, 
    1786            highlight_lines=True, 
    1787            vertical_scroll_2=self.vertical_scroll_2, 
    1788            always_hide_cursor=self.always_hide_cursor(), 
    1789            has_focus=get_app().layout.current_control == self.content, 
    1790            align=align, 
    1791            get_line_prefix=self.get_line_prefix, 
    1792        ) 
    1793 
    1794        # Remember render info. (Set before generating the margins. They need this.) 
    1795        x_offset = write_position.xpos + sum(left_margin_widths) 
    1796        y_offset = write_position.ypos 
    1797 
    1798        render_info = WindowRenderInfo( 
    1799            window=self, 
    1800            ui_content=ui_content, 
    1801            horizontal_scroll=self.horizontal_scroll, 
    1802            vertical_scroll=self.vertical_scroll, 
    1803            window_width=write_position.width - total_margin_width, 
    1804            window_height=write_position.height, 
    1805            configured_scroll_offsets=self.scroll_offsets, 
    1806            visible_line_to_row_col=visible_line_to_row_col, 
    1807            rowcol_to_yx=rowcol_to_yx, 
    1808            x_offset=x_offset, 
    1809            y_offset=y_offset, 
    1810            wrap_lines=wrap_lines, 
    1811        ) 
    1812        self.render_info = render_info 
    1813 
    1814        # Set mouse handlers. 
    1815        def mouse_handler(mouse_event: MouseEvent) -> NotImplementedOrNone: 
    1816            """ 
    1817            Wrapper around the mouse_handler of the `UIControl` that turns 
    1818            screen coordinates into line coordinates. 
    1819            Returns `NotImplemented` if no UI invalidation should be done. 
    1820            """ 
    1821            # Don't handle mouse events outside of the current modal part of 
    1822            # the UI. 
    1823            if self not in get_app().layout.walk_through_modal_area(): 
    1824                return NotImplemented 
    1825 
    1826            # Find row/col position first. 
    1827            yx_to_rowcol = {v: k for k, v in rowcol_to_yx.items()} 
    1828            y = mouse_event.position.y 
    1829            x = mouse_event.position.x 
    1830 
    1831            # If clicked below the content area, look for a position in the 
    1832            # last line instead. 
    1833            max_y = write_position.ypos + len(visible_line_to_row_col) - 1 
    1834            y = min(max_y, y) 
    1835            result: NotImplementedOrNone 
    1836 
    1837            while x >= 0: 
    1838                try: 
    1839                    row, col = yx_to_rowcol[y, x] 
    1840                except KeyError: 
    1841                    # Try again. (When clicking on the right side of double 
    1842                    # width characters, or on the right side of the input.) 
    1843                    x -= 1 
    1844                else: 
    1845                    # Found position, call handler of UIControl. 
    1846                    result = self.content.mouse_handler( 
    1847                        MouseEvent( 
    1848                            position=Point(x=col, y=row), 
    1849                            event_type=mouse_event.event_type, 
    1850                            button=mouse_event.button, 
    1851                            modifiers=mouse_event.modifiers, 
    1852                        ) 
    1853                    ) 
    1854                    break 
    1855            else: 
    1856                # nobreak. 
    1857                # (No x/y coordinate found for the content. This happens in 
    1858                # case of a DummyControl, that does not have any content. 
    1859                # Report (0,0) instead.) 
    1860                result = self.content.mouse_handler( 
    1861                    MouseEvent( 
    1862                        position=Point(x=0, y=0), 
    1863                        event_type=mouse_event.event_type, 
    1864                        button=mouse_event.button, 
    1865                        modifiers=mouse_event.modifiers, 
    1866                    ) 
    1867                ) 
    1868 
    1869            # If it returns NotImplemented, handle it here. 
    1870            if result == NotImplemented: 
    1871                result = self._mouse_handler(mouse_event) 
    1872 
    1873            return result 
    1874 
    1875        mouse_handlers.set_mouse_handler_for_range( 
    1876            x_min=write_position.xpos + sum(left_margin_widths), 
    1877            x_max=write_position.xpos + write_position.width - total_margin_width, 
    1878            y_min=write_position.ypos, 
    1879            y_max=write_position.ypos + write_position.height, 
    1880            handler=mouse_handler, 
    1881        ) 
    1882 
    1883        # Render and copy margins. 
    1884        move_x = 0 
    1885 
    1886        def render_margin(m: Margin, width: int) -> UIContent: 
    1887            "Render margin. Return `Screen`." 
    1888            # Retrieve margin fragments. 
    1889            fragments = m.create_margin(render_info, width, write_position.height) 
    1890 
    1891            # Turn it into a UIContent object. 
    1892            # already rendered those fragments using this size.) 
    1893            return FormattedTextControl(fragments).create_content( 
    1894                width + 1, write_position.height 
    1895            ) 
    1896 
    1897        for m, width in zip(self.left_margins, left_margin_widths): 
    1898            if width > 0:  # (ConditionalMargin returns a zero width. -- Don't render.) 
    1899                # Create screen for margin. 
    1900                margin_content = render_margin(m, width) 
    1901 
    1902                # Copy and shift X. 
    1903                self._copy_margin(margin_content, screen, write_position, move_x, width) 
    1904                move_x += width 
    1905 
    1906        move_x = write_position.width - sum(right_margin_widths) 
    1907 
    1908        for m, width in zip(self.right_margins, right_margin_widths): 
    1909            # Create screen for margin. 
    1910            margin_content = render_margin(m, width) 
    1911 
    1912            # Copy and shift X. 
    1913            self._copy_margin(margin_content, screen, write_position, move_x, width) 
    1914            move_x += width 
    1915 
    1916        # Apply 'self.style' 
    1917        self._apply_style(screen, write_position, parent_style) 
    1918 
    1919        # Tell the screen that this user control has been painted at this 
    1920        # position. 
    1921        screen.visible_windows_to_write_positions[self] = write_position 
    1922 
    1923    def _copy_body( 
    1924        self, 
    1925        ui_content: UIContent, 
    1926        new_screen: Screen, 
    1927        write_position: WritePosition, 
    1928        move_x: int, 
    1929        width: int, 
    1930        vertical_scroll: int = 0, 
    1931        horizontal_scroll: int = 0, 
    1932        wrap_lines: bool = False, 
    1933        highlight_lines: bool = False, 
    1934        vertical_scroll_2: int = 0, 
    1935        always_hide_cursor: bool = False, 
    1936        has_focus: bool = False, 
    1937        align: WindowAlign = WindowAlign.LEFT, 
    1938        get_line_prefix: Callable[[int, int], AnyFormattedText] | None = None, 
    1939    ) -> tuple[dict[int, tuple[int, int]], dict[tuple[int, int], tuple[int, int]]]: 
    1940        """ 
    1941        Copy the UIContent into the output screen. 
    1942        Return (visible_line_to_row_col, rowcol_to_yx) tuple. 
    1943 
    1944        :param get_line_prefix: None or a callable that takes a line number 
    1945            (int) and a wrap_count (int) and returns formatted text. 
    1946        """ 
    1947        xpos = write_position.xpos + move_x 
    1948        ypos = write_position.ypos 
    1949        line_count = ui_content.line_count 
    1950        new_buffer = new_screen.data_buffer 
    1951        empty_char = _CHAR_CACHE["", ""] 
    1952 
    1953        # Map visible line number to (row, col) of input. 
    1954        # 'col' will always be zero if line wrapping is off. 
    1955        visible_line_to_row_col: dict[int, tuple[int, int]] = {} 
    1956 
    1957        # Maps (row, col) from the input to (y, x) screen coordinates. 
    1958        rowcol_to_yx: dict[tuple[int, int], tuple[int, int]] = {} 
    1959 
    1960        def copy_line( 
    1961            line: StyleAndTextTuples, 
    1962            lineno: int, 
    1963            x: int, 
    1964            y: int, 
    1965            is_input: bool = False, 
    1966        ) -> tuple[int, int]: 
    1967            """ 
    1968            Copy over a single line to the output screen. This can wrap over 
    1969            multiple lines in the output. It will call the prefix (prompt) 
    1970            function before every line. 
    1971            """ 
    1972            if is_input: 
    1973                current_rowcol_to_yx = rowcol_to_yx 
    1974            else: 
    1975                current_rowcol_to_yx = {}  # Throwaway dictionary. 
    1976 
    1977            # Draw line prefix. 
    1978            if is_input and get_line_prefix: 
    1979                prompt = to_formatted_text(get_line_prefix(lineno, 0)) 
    1980                x, y = copy_line(prompt, lineno, x, y, is_input=False) 
    1981 
    1982            # Scroll horizontally. 
    1983            skipped = 0  # Characters skipped because of horizontal scrolling. 
    1984            if horizontal_scroll and is_input: 
    1985                h_scroll = horizontal_scroll 
    1986                line = explode_text_fragments(line) 
    1987                while h_scroll > 0 and line: 
    1988                    h_scroll -= get_cwidth(line[0][1]) 
    1989                    skipped += 1 
    1990                    del line[:1]  # Remove first character. 
    1991 
    1992                x -= h_scroll  # When scrolling over double width character, 
    1993                # this can end up being negative. 
    1994 
    1995            # Align this line. (Note that this doesn't work well when we use 
    1996            # get_line_prefix and that function returns variable width prefixes.) 
    1997            if align == WindowAlign.CENTER: 
    1998                line_width = fragment_list_width(line) 
    1999                if line_width < width: 
    2000                    x += (width - line_width) // 2 
    2001            elif align == WindowAlign.RIGHT: 
    2002                line_width = fragment_list_width(line) 
    2003                if line_width < width: 
    2004                    x += width - line_width 
    2005 
    2006            col = 0 
    2007            wrap_count = 0 
    2008            for style, text, *_ in line: 
    2009                new_buffer_row = new_buffer[y + ypos] 
    2010 
    2011                # Remember raw VT escape sequences. (E.g. FinalTerm's 
    2012                # escape sequences.) 
    2013                if "[ZeroWidthEscape]" in style: 
    2014                    new_screen.zero_width_escapes[y + ypos][x + xpos] += text 
    2015                    continue 
    2016 
    2017                for c in text: 
    2018                    char = _CHAR_CACHE[c, style] 
    2019                    char_width = char.width 
    2020 
    2021                    # Wrap when the line width is exceeded. 
    2022                    if wrap_lines and x + char_width > width: 
    2023                        visible_line_to_row_col[y + 1] = ( 
    2024                            lineno, 
    2025                            visible_line_to_row_col[y][1] + x, 
    2026                        ) 
    2027                        y += 1 
    2028                        wrap_count += 1 
    2029                        x = 0 
    2030 
    2031                        # Insert line prefix (continuation prompt). 
    2032                        if is_input and get_line_prefix: 
    2033                            prompt = to_formatted_text( 
    2034                                get_line_prefix(lineno, wrap_count) 
    2035                            ) 
    2036                            x, y = copy_line(prompt, lineno, x, y, is_input=False) 
    2037 
    2038                        new_buffer_row = new_buffer[y + ypos] 
    2039 
    2040                        if y >= write_position.height: 
    2041                            return x, y  # Break out of all for loops. 
    2042 
    2043                    # Set character in screen and shift 'x'. 
    2044                    if x >= 0 and y >= 0 and x < width: 
    2045                        new_buffer_row[x + xpos] = char 
    2046 
    2047                        # When we print a multi width character, make sure 
    2048                        # to erase the neighbors positions in the screen. 
    2049                        # (The empty string if different from everything, 
    2050                        # so next redraw this cell will repaint anyway.) 
    2051                        if char_width > 1: 
    2052                            for i in range(1, char_width): 
    2053                                new_buffer_row[x + xpos + i] = empty_char 
    2054 
    2055                        # If this is a zero width characters, then it's 
    2056                        # probably part of a decomposed unicode character. 
    2057                        # See: https://en.wikipedia.org/wiki/Unicode_equivalence 
    2058                        # Merge it in the previous cell. 
    2059                        elif char_width == 0: 
    2060                            # Handle all character widths. If the previous 
    2061                            # character is a multiwidth character, then 
    2062                            # merge it two positions back. 
    2063                            for pw in [2, 1]:  # Previous character width. 
    2064                                if ( 
    2065                                    x - pw >= 0 
    2066                                    and new_buffer_row[x + xpos - pw].width == pw 
    2067                                ): 
    2068                                    prev_char = new_buffer_row[x + xpos - pw] 
    2069                                    char2 = _CHAR_CACHE[ 
    2070                                        prev_char.char + c, prev_char.style 
    2071                                    ] 
    2072                                    new_buffer_row[x + xpos - pw] = char2 
    2073 
    2074                        # Keep track of write position for each character. 
    2075                        current_rowcol_to_yx[lineno, col + skipped] = ( 
    2076                            y + ypos, 
    2077                            x + xpos, 
    2078                        ) 
    2079 
    2080                    col += 1 
    2081                    x += char_width 
    2082            return x, y 
    2083 
    2084        # Copy content. 
    2085        def copy() -> int: 
    2086            y = -vertical_scroll_2 
    2087            lineno = vertical_scroll 
    2088 
    2089            while y < write_position.height and lineno < line_count: 
    2090                # Take the next line and copy it in the real screen. 
    2091                line = ui_content.get_line(lineno) 
    2092 
    2093                visible_line_to_row_col[y] = (lineno, horizontal_scroll) 
    2094 
    2095                # Copy margin and actual line. 
    2096                x = 0 
    2097                x, y = copy_line(line, lineno, x, y, is_input=True) 
    2098 
    2099                lineno += 1 
    2100                y += 1 
    2101            return y 
    2102 
    2103        copy() 
    2104 
    2105        def cursor_pos_to_screen_pos(row: int, col: int) -> Point: 
    2106            "Translate row/col from UIContent to real Screen coordinates." 
    2107            try: 
    2108                y, x = rowcol_to_yx[row, col] 
    2109            except KeyError: 
    2110                # Normally this should never happen. (It is a bug, if it happens.) 
    2111                # But to be sure, return (0, 0) 
    2112                return Point(x=0, y=0) 
    2113 
    2114                # raise ValueError( 
    2115                #     'Invalid position. row=%r col=%r, vertical_scroll=%r, ' 
    2116                #     'horizontal_scroll=%r, height=%r' % 
    2117                #     (row, col, vertical_scroll, horizontal_scroll, write_position.height)) 
    2118            else: 
    2119                return Point(x=x, y=y) 
    2120 
    2121        # Set cursor and menu positions. 
    2122        if ui_content.cursor_position: 
    2123            screen_cursor_position = cursor_pos_to_screen_pos( 
    2124                ui_content.cursor_position.y, ui_content.cursor_position.x 
    2125            ) 
    2126 
    2127            if has_focus: 
    2128                new_screen.set_cursor_position(self, screen_cursor_position) 
    2129 
    2130                if always_hide_cursor: 
    2131                    new_screen.show_cursor = False 
    2132                else: 
    2133                    new_screen.show_cursor = ui_content.show_cursor 
    2134 
    2135                self._highlight_digraph(new_screen) 
    2136 
    2137            if highlight_lines: 
    2138                self._highlight_cursorlines( 
    2139                    new_screen, 
    2140                    screen_cursor_position, 
    2141                    xpos, 
    2142                    ypos, 
    2143                    width, 
    2144                    write_position.height, 
    2145                ) 
    2146 
    2147        # Draw input characters from the input processor queue. 
    2148        if has_focus and ui_content.cursor_position: 
    2149            self._show_key_processor_key_buffer(new_screen) 
    2150 
    2151        # Set menu position. 
    2152        if ui_content.menu_position: 
    2153            new_screen.set_menu_position( 
    2154                self, 
    2155                cursor_pos_to_screen_pos( 
    2156                    ui_content.menu_position.y, ui_content.menu_position.x 
    2157                ), 
    2158            ) 
    2159 
    2160        # Update output screen height. 
    2161        new_screen.height = max(new_screen.height, ypos + write_position.height) 
    2162 
    2163        return visible_line_to_row_col, rowcol_to_yx 
    2164 
    2165    def _fill_bg( 
    2166        self, screen: Screen, write_position: WritePosition, erase_bg: bool 
    2167    ) -> None: 
    2168        """ 
    2169        Erase/fill the background. 
    2170        (Useful for floats and when a `char` has been given.) 
    2171        """ 
    2172        char: str | None 
    2173        if callable(self.char): 
    2174            char = self.char() 
    2175        else: 
    2176            char = self.char 
    2177 
    2178        if erase_bg or char: 
    2179            wp = write_position 
    2180            char_obj = _CHAR_CACHE[char or " ", ""] 
    2181 
    2182            for y in range(wp.ypos, wp.ypos + wp.height): 
    2183                row = screen.data_buffer[y] 
    2184                for x in range(wp.xpos, wp.xpos + wp.width): 
    2185                    row[x] = char_obj 
    2186 
    2187    def _apply_style( 
    2188        self, new_screen: Screen, write_position: WritePosition, parent_style: str 
    2189    ) -> None: 
    2190        # Apply `self.style`. 
    2191        style = parent_style + " " + to_str(self.style) 
    2192 
    2193        new_screen.fill_area(write_position, style=style, after=False) 
    2194 
    2195        # Apply the 'last-line' class to the last line of each Window. This can 
    2196        # be used to apply an 'underline' to the user control. 
    2197        wp = WritePosition( 
    2198            write_position.xpos, 
    2199            write_position.ypos + write_position.height - 1, 
    2200            write_position.width, 
    2201            1, 
    2202        ) 
    2203        new_screen.fill_area(wp, "class:last-line", after=True) 
    2204 
    2205    def _highlight_digraph(self, new_screen: Screen) -> None: 
    2206        """ 
    2207        When we are in Vi digraph mode, put a question mark underneath the 
    2208        cursor. 
    2209        """ 
    2210        digraph_char = self._get_digraph_char() 
    2211        if digraph_char: 
    2212            cpos = new_screen.get_cursor_position(self) 
    2213            new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[ 
    2214                digraph_char, "class:digraph" 
    2215            ] 
    2216 
    2217    def _show_key_processor_key_buffer(self, new_screen: Screen) -> None: 
    2218        """ 
    2219        When the user is typing a key binding that consists of several keys, 
    2220        display the last pressed key if the user is in insert mode and the key 
    2221        is meaningful to be displayed. 
    2222        E.g. Some people want to bind 'jj' to escape in Vi insert mode. But the 
    2223             first 'j' needs to be displayed in order to get some feedback. 
    2224        """ 
    2225        app = get_app() 
    2226        key_buffer = app.key_processor.key_buffer 
    2227 
    2228        if key_buffer and _in_insert_mode() and not app.is_done: 
    2229            # The textual data for the given key. (Can be a VT100 escape 
    2230            # sequence.) 
    2231            data = key_buffer[-1].data 
    2232 
    2233            # Display only if this is a 1 cell width character. 
    2234            if get_cwidth(data) == 1: 
    2235                cpos = new_screen.get_cursor_position(self) 
    2236                new_screen.data_buffer[cpos.y][cpos.x] = _CHAR_CACHE[ 
    2237                    data, "class:partial-key-binding" 
    2238                ] 
    2239 
    2240    def _highlight_cursorlines( 
    2241        self, new_screen: Screen, cpos: Point, x: int, y: int, width: int, height: int 
    2242    ) -> None: 
    2243        """ 
    2244        Highlight cursor row/column. 
    2245        """ 
    2246        cursor_line_style = " class:cursor-line " 
    2247        cursor_column_style = " class:cursor-column " 
    2248 
    2249        data_buffer = new_screen.data_buffer 
    2250 
    2251        # Highlight cursor line. 
    2252        if self.cursorline(): 
    2253            row = data_buffer[cpos.y] 
    2254            for x in range(x, x + width): 
    2255                original_char = row[x] 
    2256                row[x] = _CHAR_CACHE[ 
    2257                    original_char.char, original_char.style + cursor_line_style 
    2258                ] 
    2259 
    2260        # Highlight cursor column. 
    2261        if self.cursorcolumn(): 
    2262            for y2 in range(y, y + height): 
    2263                row = data_buffer[y2] 
    2264                original_char = row[cpos.x] 
    2265                row[cpos.x] = _CHAR_CACHE[ 
    2266                    original_char.char, original_char.style + cursor_column_style 
    2267                ] 
    2268 
    2269        # Highlight color columns 
    2270        colorcolumns = self.colorcolumns 
    2271        if callable(colorcolumns): 
    2272            colorcolumns = colorcolumns() 
    2273 
    2274        for cc in colorcolumns: 
    2275            assert isinstance(cc, ColorColumn) 
    2276            column = cc.position 
    2277 
    2278            if column < x + width:  # Only draw when visible. 
    2279                color_column_style = " " + cc.style 
    2280 
    2281                for y2 in range(y, y + height): 
    2282                    row = data_buffer[y2] 
    2283                    original_char = row[column + x] 
    2284                    row[column + x] = _CHAR_CACHE[ 
    2285                        original_char.char, original_char.style + color_column_style 
    2286                    ] 
    2287 
    2288    def _copy_margin( 
    2289        self, 
    2290        margin_content: UIContent, 
    2291        new_screen: Screen, 
    2292        write_position: WritePosition, 
    2293        move_x: int, 
    2294        width: int, 
    2295    ) -> None: 
    2296        """ 
    2297        Copy characters from the margin screen to the real screen. 
    2298        """ 
    2299        xpos = write_position.xpos + move_x 
    2300        ypos = write_position.ypos 
    2301 
    2302        margin_write_position = WritePosition(xpos, ypos, width, write_position.height) 
    2303        self._copy_body(margin_content, new_screen, margin_write_position, 0, width) 
    2304 
    2305    def _scroll(self, ui_content: UIContent, width: int, height: int) -> None: 
    2306        """ 
    2307        Scroll body. Ensure that the cursor is visible. 
    2308        """ 
    2309        if self.wrap_lines(): 
    2310            func = self._scroll_when_linewrapping 
    2311        else: 
    2312            func = self._scroll_without_linewrapping 
    2313 
    2314        func(ui_content, width, height) 
    2315 
    2316    def _scroll_when_linewrapping( 
    2317        self, ui_content: UIContent, width: int, height: int 
    2318    ) -> None: 
    2319        """ 
    2320        Scroll to make sure the cursor position is visible and that we maintain 
    2321        the requested scroll offset. 
    2322 
    2323        Set `self.horizontal_scroll/vertical_scroll`. 
    2324        """ 
    2325        scroll_offsets_bottom = self.scroll_offsets.bottom 
    2326        scroll_offsets_top = self.scroll_offsets.top 
    2327 
    2328        # We don't have horizontal scrolling. 
    2329        self.horizontal_scroll = 0 
    2330 
    2331        def get_line_height(lineno: int) -> int: 
    2332            return ui_content.get_height_for_line(lineno, width, self.get_line_prefix) 
    2333 
    2334        # When there is no space, reset `vertical_scroll_2` to zero and abort. 
    2335        # This can happen if the margin is bigger than the window width. 
    2336        # Otherwise the text height will become "infinite" (a big number) and 
    2337        # the copy_line will spend a huge amount of iterations trying to render 
    2338        # nothing. 
    2339        if width <= 0: 
    2340            self.vertical_scroll = ui_content.cursor_position.y 
    2341            self.vertical_scroll_2 = 0 
    2342            return 
    2343 
    2344        # If the current line consumes more than the whole window height, 
    2345        # then we have to scroll vertically inside this line. (We don't take 
    2346        # the scroll offsets into account for this.) 
    2347        # Also, ignore the scroll offsets in this case. Just set the vertical 
    2348        # scroll to this line. 
    2349        line_height = get_line_height(ui_content.cursor_position.y) 
    2350        if line_height > height - scroll_offsets_top: 
    2351            # Calculate the height of the text before the cursor (including 
    2352            # line prefixes). 
    2353            text_before_height = ui_content.get_height_for_line( 
    2354                ui_content.cursor_position.y, 
    2355                width, 
    2356                self.get_line_prefix, 
    2357                slice_stop=ui_content.cursor_position.x, 
    2358            ) 
    2359 
    2360            # Adjust scroll offset. 
    2361            self.vertical_scroll = ui_content.cursor_position.y 
    2362            self.vertical_scroll_2 = min( 
    2363                text_before_height - 1,  # Keep the cursor visible. 
    2364                line_height 
    2365                - height,  # Avoid blank lines at the bottom when scrolling up again. 
    2366                self.vertical_scroll_2, 
    2367            ) 
    2368            self.vertical_scroll_2 = max( 
    2369                0, text_before_height - height, self.vertical_scroll_2 
    2370            ) 
    2371            return 
    2372        else: 
    2373            self.vertical_scroll_2 = 0 
    2374 
    2375        # Current line doesn't consume the whole height. Take scroll offsets into account. 
    2376        def get_min_vertical_scroll() -> int: 
    2377            # Make sure that the cursor line is not below the bottom. 
    2378            # (Calculate how many lines can be shown between the cursor and the .) 
    2379            used_height = 0 
    2380            prev_lineno = ui_content.cursor_position.y 
    2381 
    2382            for lineno in range(ui_content.cursor_position.y, -1, -1): 
    2383                used_height += get_line_height(lineno) 
    2384 
    2385                if used_height > height - scroll_offsets_bottom: 
    2386                    return prev_lineno 
    2387                else: 
    2388                    prev_lineno = lineno 
    2389            return 0 
    2390 
    2391        def get_max_vertical_scroll() -> int: 
    2392            # Make sure that the cursor line is not above the top. 
    2393            prev_lineno = ui_content.cursor_position.y 
    2394            used_height = 0 
    2395 
    2396            for lineno in range(ui_content.cursor_position.y - 1, -1, -1): 
    2397                used_height += get_line_height(lineno) 
    2398 
    2399                if used_height > scroll_offsets_top: 
    2400                    return prev_lineno 
    2401                else: 
    2402                    prev_lineno = lineno 
    2403            return prev_lineno 
    2404 
    2405        def get_topmost_visible() -> int: 
    2406            """ 
    2407            Calculate the upper most line that can be visible, while the bottom 
    2408            is still visible. We should not allow scroll more than this if 
    2409            `allow_scroll_beyond_bottom` is false. 
    2410            """ 
    2411            prev_lineno = ui_content.line_count - 1 
    2412            used_height = 0 
    2413            for lineno in range(ui_content.line_count - 1, -1, -1): 
    2414                used_height += get_line_height(lineno) 
    2415                if used_height > height: 
    2416                    return prev_lineno 
    2417                else: 
    2418                    prev_lineno = lineno 
    2419            return prev_lineno 
    2420 
    2421        # Scroll vertically. (Make sure that the whole line which contains the 
    2422        # cursor is visible. 
    2423        topmost_visible = get_topmost_visible() 
    2424 
    2425        # Note: the `min(topmost_visible, ...)` is to make sure that we 
    2426        # don't require scrolling up because of the bottom scroll offset, 
    2427        # when we are at the end of the document. 
    2428        self.vertical_scroll = max( 
    2429            self.vertical_scroll, min(topmost_visible, get_min_vertical_scroll()) 
    2430        ) 
    2431        self.vertical_scroll = min(self.vertical_scroll, get_max_vertical_scroll()) 
    2432 
    2433        # Disallow scrolling beyond bottom? 
    2434        if not self.allow_scroll_beyond_bottom(): 
    2435            self.vertical_scroll = min(self.vertical_scroll, topmost_visible) 
    2436 
    2437    def _scroll_without_linewrapping( 
    2438        self, ui_content: UIContent, width: int, height: int 
    2439    ) -> None: 
    2440        """ 
    2441        Scroll to make sure the cursor position is visible and that we maintain 
    2442        the requested scroll offset. 
    2443 
    2444        Set `self.horizontal_scroll/vertical_scroll`. 
    2445        """ 
    2446        cursor_position = ui_content.cursor_position or Point(x=0, y=0) 
    2447 
    2448        # Without line wrapping, we will never have to scroll vertically inside 
    2449        # a single line. 
    2450        self.vertical_scroll_2 = 0 
    2451 
    2452        if ui_content.line_count == 0: 
    2453            self.vertical_scroll = 0 
    2454            self.horizontal_scroll = 0 
    2455            return 
    2456        else: 
    2457            current_line_text = fragment_list_to_text( 
    2458                ui_content.get_line(cursor_position.y) 
    2459            ) 
    2460 
    2461        def do_scroll( 
    2462            current_scroll: int, 
    2463            scroll_offset_start: int, 
    2464            scroll_offset_end: int, 
    2465            cursor_pos: int, 
    2466            window_size: int, 
    2467            content_size: int, 
    2468        ) -> int: 
    2469            "Scrolling algorithm. Used for both horizontal and vertical scrolling." 
    2470            # Calculate the scroll offset to apply. 
    2471            # This can obviously never be more than have the screen size. Also, when the 
    2472            # cursor appears at the top or bottom, we don't apply the offset. 
    2473            scroll_offset_start = int( 
    2474                min(scroll_offset_start, window_size / 2, cursor_pos) 
    2475            ) 
    2476            scroll_offset_end = int( 
    2477                min(scroll_offset_end, window_size / 2, content_size - 1 - cursor_pos) 
    2478            ) 
    2479 
    2480            # Prevent negative scroll offsets. 
    2481            if current_scroll < 0: 
    2482                current_scroll = 0 
    2483 
    2484            # Scroll back if we scrolled to much and there's still space to show more of the document. 
    2485            if ( 
    2486                not self.allow_scroll_beyond_bottom() 
    2487                and current_scroll > content_size - window_size 
    2488            ): 
    2489                current_scroll = max(0, content_size - window_size) 
    2490 
    2491            # Scroll up if cursor is before visible part. 
    2492            if current_scroll > cursor_pos - scroll_offset_start: 
    2493                current_scroll = max(0, cursor_pos - scroll_offset_start) 
    2494 
    2495            # Scroll down if cursor is after visible part. 
    2496            if current_scroll < (cursor_pos + 1) - window_size + scroll_offset_end: 
    2497                current_scroll = (cursor_pos + 1) - window_size + scroll_offset_end 
    2498 
    2499            return current_scroll 
    2500 
    2501        # When a preferred scroll is given, take that first into account. 
    2502        if self.get_vertical_scroll: 
    2503            self.vertical_scroll = self.get_vertical_scroll(self) 
    2504            assert isinstance(self.vertical_scroll, int) 
    2505        if self.get_horizontal_scroll: 
    2506            self.horizontal_scroll = self.get_horizontal_scroll(self) 
    2507            assert isinstance(self.horizontal_scroll, int) 
    2508 
    2509        # Update horizontal/vertical scroll to make sure that the cursor 
    2510        # remains visible. 
    2511        offsets = self.scroll_offsets 
    2512 
    2513        self.vertical_scroll = do_scroll( 
    2514            current_scroll=self.vertical_scroll, 
    2515            scroll_offset_start=offsets.top, 
    2516            scroll_offset_end=offsets.bottom, 
    2517            cursor_pos=ui_content.cursor_position.y, 
    2518            window_size=height, 
    2519            content_size=ui_content.line_count, 
    2520        ) 
    2521 
    2522        if self.get_line_prefix: 
    2523            current_line_prefix_width = fragment_list_width( 
    2524                to_formatted_text(self.get_line_prefix(ui_content.cursor_position.y, 0)) 
    2525            ) 
    2526        else: 
    2527            current_line_prefix_width = 0 
    2528 
    2529        self.horizontal_scroll = do_scroll( 
    2530            current_scroll=self.horizontal_scroll, 
    2531            scroll_offset_start=offsets.left, 
    2532            scroll_offset_end=offsets.right, 
    2533            cursor_pos=get_cwidth(current_line_text[: ui_content.cursor_position.x]), 
    2534            window_size=width - current_line_prefix_width, 
    2535            # We can only analyze the current line. Calculating the width off 
    2536            # all the lines is too expensive. 
    2537            content_size=max( 
    2538                get_cwidth(current_line_text), self.horizontal_scroll + width 
    2539            ), 
    2540        ) 
    2541 
    2542    def _mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: 
    2543        """ 
    2544        Mouse handler. Called when the UI control doesn't handle this 
    2545        particular event. 
    2546 
    2547        Return `NotImplemented` if nothing was done as a consequence of this 
    2548        key binding (no UI invalidate required in that case). 
    2549        """ 
    2550        if mouse_event.event_type == MouseEventType.SCROLL_DOWN: 
    2551            self._scroll_down() 
    2552            return None 
    2553        elif mouse_event.event_type == MouseEventType.SCROLL_UP: 
    2554            self._scroll_up() 
    2555            return None 
    2556 
    2557        return NotImplemented 
    2558 
    2559    def _scroll_down(self) -> None: 
    2560        "Scroll window down." 
    2561        info = self.render_info 
    2562 
    2563        if info is None: 
    2564            return 
    2565 
    2566        if self.vertical_scroll < info.content_height - info.window_height: 
    2567            if info.cursor_position.y <= info.configured_scroll_offsets.top: 
    2568                self.content.move_cursor_down() 
    2569 
    2570            self.vertical_scroll += 1 
    2571 
    2572    def _scroll_up(self) -> None: 
    2573        "Scroll window up." 
    2574        info = self.render_info 
    2575 
    2576        if info is None: 
    2577            return 
    2578 
    2579        if info.vertical_scroll > 0: 
    2580            # TODO: not entirely correct yet in case of line wrapping and long lines. 
    2581            if ( 
    2582                info.cursor_position.y 
    2583                >= info.window_height - 1 - info.configured_scroll_offsets.bottom 
    2584            ): 
    2585                self.content.move_cursor_up() 
    2586 
    2587            self.vertical_scroll -= 1 
    2588 
    2589    def get_key_bindings(self) -> KeyBindingsBase | None: 
    2590        return self.content.get_key_bindings() 
    2591 
    2592    def get_children(self) -> list[Container]: 
    2593        return [] 
    2594 
    2595 
    2596class ConditionalContainer(Container): 
    2597    """ 
    2598    Wrapper around any other container that can change the visibility. The 
    2599    received `filter` determines whether the given container should be 
    2600    displayed or not. 
    2601 
    2602    :param content: :class:`.Container` instance. 
    2603    :param filter: :class:`.Filter` instance. 
    2604    """ 
    2605 
    2606    def __init__( 
    2607        self, 
    2608        content: AnyContainer, 
    2609        filter: FilterOrBool, 
    2610        alternative_content: AnyContainer | None = None, 
    2611    ) -> None: 
    2612        self.content = to_container(content) 
    2613        self.alternative_content = ( 
    2614            to_container(alternative_content) 
    2615            if alternative_content is not None 
    2616            else None 
    2617        ) 
    2618        self.filter = to_filter(filter) 
    2619 
    2620    def __repr__(self) -> str: 
    2621        return f"ConditionalContainer({self.content!r}, filter={self.filter!r})" 
    2622 
    2623    def reset(self) -> None: 
    2624        self.content.reset() 
    2625 
    2626    def preferred_width(self, max_available_width: int) -> Dimension: 
    2627        if self.filter(): 
    2628            return self.content.preferred_width(max_available_width) 
    2629        elif self.alternative_content is not None: 
    2630            return self.alternative_content.preferred_width(max_available_width) 
    2631        else: 
    2632            return Dimension.zero() 
    2633 
    2634    def preferred_height(self, width: int, max_available_height: int) -> Dimension: 
    2635        if self.filter(): 
    2636            return self.content.preferred_height(width, max_available_height) 
    2637        elif self.alternative_content is not None: 
    2638            return self.alternative_content.preferred_height( 
    2639                width, max_available_height 
    2640            ) 
    2641        else: 
    2642            return Dimension.zero() 
    2643 
    2644    def write_to_screen( 
    2645        self, 
    2646        screen: Screen, 
    2647        mouse_handlers: MouseHandlers, 
    2648        write_position: WritePosition, 
    2649        parent_style: str, 
    2650        erase_bg: bool, 
    2651        z_index: int | None, 
    2652    ) -> None: 
    2653        if self.filter(): 
    2654            return self.content.write_to_screen( 
    2655                screen, mouse_handlers, write_position, parent_style, erase_bg, z_index 
    2656            ) 
    2657        elif self.alternative_content is not None: 
    2658            return self.alternative_content.write_to_screen( 
    2659                screen, 
    2660                mouse_handlers, 
    2661                write_position, 
    2662                parent_style, 
    2663                erase_bg, 
    2664                z_index, 
    2665            ) 
    2666 
    2667    def get_children(self) -> list[Container]: 
    2668        result = [self.content] 
    2669        if self.alternative_content is not None: 
    2670            result.append(self.alternative_content) 
    2671        return result 
    2672 
    2673 
    2674class DynamicContainer(Container): 
    2675    """ 
    2676    Container class that dynamically returns any Container. 
    2677 
    2678    :param get_container: Callable that returns a :class:`.Container` instance 
    2679        or any widget with a ``__pt_container__`` method. 
    2680    """ 
    2681 
    2682    def __init__(self, get_container: Callable[[], AnyContainer]) -> None: 
    2683        self.get_container = get_container 
    2684 
    2685    def _get_container(self) -> Container: 
    2686        """ 
    2687        Return the current container object. 
    2688 
    2689        We call `to_container`, because `get_container` can also return a 
    2690        widget with a ``__pt_container__`` method. 
    2691        """ 
    2692        obj = self.get_container() 
    2693        return to_container(obj) 
    2694 
    2695    def reset(self) -> None: 
    2696        self._get_container().reset() 
    2697 
    2698    def preferred_width(self, max_available_width: int) -> Dimension: 
    2699        return self._get_container().preferred_width(max_available_width) 
    2700 
    2701    def preferred_height(self, width: int, max_available_height: int) -> Dimension: 
    2702        return self._get_container().preferred_height(width, max_available_height) 
    2703 
    2704    def write_to_screen( 
    2705        self, 
    2706        screen: Screen, 
    2707        mouse_handlers: MouseHandlers, 
    2708        write_position: WritePosition, 
    2709        parent_style: str, 
    2710        erase_bg: bool, 
    2711        z_index: int | None, 
    2712    ) -> None: 
    2713        self._get_container().write_to_screen( 
    2714            screen, mouse_handlers, write_position, parent_style, erase_bg, z_index 
    2715        ) 
    2716 
    2717    def is_modal(self) -> bool: 
    2718        return False 
    2719 
    2720    def get_key_bindings(self) -> KeyBindingsBase | None: 
    2721        # Key bindings will be collected when `layout.walk()` finds the child 
    2722        # container. 
    2723        return None 
    2724 
    2725    def get_children(self) -> list[Container]: 
    2726        # Here we have to return the current active container itself, not its 
    2727        # children. Otherwise, we run into issues where `layout.walk()` will 
    2728        # never see an object of type `Window` if this contains a window. We 
    2729        # can't/shouldn't proxy the "isinstance" check. 
    2730        return [self._get_container()] 
    2731 
    2732 
    2733def to_container(container: AnyContainer) -> Container: 
    2734    """ 
    2735    Make sure that the given object is a :class:`.Container`. 
    2736    """ 
    2737    if isinstance(container, Container): 
    2738        return container 
    2739    elif hasattr(container, "__pt_container__"): 
    2740        return to_container(container.__pt_container__()) 
    2741    else: 
    2742        raise ValueError(f"Not a container object: {container!r}") 
    2743 
    2744 
    2745def to_window(container: AnyContainer) -> Window: 
    2746    """ 
    2747    Make sure that the given argument is a :class:`.Window`. 
    2748    """ 
    2749    if isinstance(container, Window): 
    2750        return container 
    2751    elif hasattr(container, "__pt_container__"): 
    2752        return to_window(cast("MagicContainer", container).__pt_container__()) 
    2753    else: 
    2754        raise ValueError(f"Not a Window object: {container!r}.") 
    2755 
    2756 
    2757def is_container(value: object) -> TypeGuard[AnyContainer]: 
    2758    """ 
    2759    Checks whether the given value is a container object 
    2760    (for use in assert statements). 
    2761    """ 
    2762    if isinstance(value, Container): 
    2763        return True 
    2764    if hasattr(value, "__pt_container__"): 
    2765        return is_container(cast("MagicContainer", value).__pt_container__()) 
    2766    return False