1""" 
    2User interface Controls for the layout. 
    3""" 
    4 
    5from __future__ import annotations 
    6 
    7import time 
    8from abc import ABCMeta, abstractmethod 
    9from typing import TYPE_CHECKING, Callable, Hashable, Iterable, NamedTuple 
    10 
    11from prompt_toolkit.application.current import get_app 
    12from prompt_toolkit.buffer import Buffer 
    13from prompt_toolkit.cache import SimpleCache 
    14from prompt_toolkit.data_structures import Point 
    15from prompt_toolkit.document import Document 
    16from prompt_toolkit.filters import FilterOrBool, to_filter 
    17from prompt_toolkit.formatted_text import ( 
    18    AnyFormattedText, 
    19    StyleAndTextTuples, 
    20    to_formatted_text, 
    21) 
    22from prompt_toolkit.formatted_text.utils import ( 
    23    fragment_list_to_text, 
    24    fragment_list_width, 
    25    split_lines, 
    26) 
    27from prompt_toolkit.lexers import Lexer, SimpleLexer 
    28from prompt_toolkit.mouse_events import MouseButton, MouseEvent, MouseEventType 
    29from prompt_toolkit.search import SearchState 
    30from prompt_toolkit.selection import SelectionType 
    31from prompt_toolkit.utils import get_cwidth 
    32 
    33from .processors import ( 
    34    DisplayMultipleCursors, 
    35    HighlightIncrementalSearchProcessor, 
    36    HighlightSearchProcessor, 
    37    HighlightSelectionProcessor, 
    38    Processor, 
    39    TransformationInput, 
    40    merge_processors, 
    41) 
    42 
    43if TYPE_CHECKING: 
    44    from prompt_toolkit.key_binding.key_bindings import ( 
    45        KeyBindingsBase, 
    46        NotImplementedOrNone, 
    47    ) 
    48    from prompt_toolkit.utils import Event 
    49 
    50 
    51__all__ = [ 
    52    "BufferControl", 
    53    "SearchBufferControl", 
    54    "DummyControl", 
    55    "FormattedTextControl", 
    56    "UIControl", 
    57    "UIContent", 
    58] 
    59 
    60GetLinePrefixCallable = Callable[[int, int], AnyFormattedText] 
    61 
    62 
    63class UIControl(metaclass=ABCMeta): 
    64    """ 
    65    Base class for all user interface controls. 
    66    """ 
    67 
    68    def reset(self) -> None: 
    69        # Default reset. (Doesn't have to be implemented.) 
    70        pass 
    71 
    72    def preferred_width(self, max_available_width: int) -> int | None: 
    73        return None 
    74 
    75    def preferred_height( 
    76        self, 
    77        width: int, 
    78        max_available_height: int, 
    79        wrap_lines: bool, 
    80        get_line_prefix: GetLinePrefixCallable | None, 
    81    ) -> int | None: 
    82        return None 
    83 
    84    def is_focusable(self) -> bool: 
    85        """ 
    86        Tell whether this user control is focusable. 
    87        """ 
    88        return False 
    89 
    90    @abstractmethod 
    91    def create_content(self, width: int, height: int) -> UIContent: 
    92        """ 
    93        Generate the content for this user control. 
    94 
    95        Returns a :class:`.UIContent` instance. 
    96        """ 
    97 
    98    def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: 
    99        """ 
    100        Handle mouse events. 
    101 
    102        When `NotImplemented` is returned, it means that the given event is not 
    103        handled by the `UIControl` itself. The `Window` or key bindings can 
    104        decide to handle this event as scrolling or changing focus. 
    105 
    106        :param mouse_event: `MouseEvent` instance. 
    107        """ 
    108        return NotImplemented 
    109 
    110    def move_cursor_down(self) -> None: 
    111        """ 
    112        Request to move the cursor down. 
    113        This happens when scrolling down and the cursor is completely at the 
    114        top. 
    115        """ 
    116 
    117    def move_cursor_up(self) -> None: 
    118        """ 
    119        Request to move the cursor up. 
    120        """ 
    121 
    122    def get_key_bindings(self) -> KeyBindingsBase | None: 
    123        """ 
    124        The key bindings that are specific for this user control. 
    125 
    126        Return a :class:`.KeyBindings` object if some key bindings are 
    127        specified, or `None` otherwise. 
    128        """ 
    129 
    130    def get_invalidate_events(self) -> Iterable[Event[object]]: 
    131        """ 
    132        Return a list of `Event` objects. This can be a generator. 
    133        (The application collects all these events, in order to bind redraw 
    134        handlers to these events.) 
    135        """ 
    136        return [] 
    137 
    138 
    139class UIContent: 
    140    """ 
    141    Content generated by a user control. This content consists of a list of 
    142    lines. 
    143 
    144    :param get_line: Callable that takes a line number and returns the current 
    145        line. This is a list of (style_str, text) tuples. 
    146    :param line_count: The number of lines. 
    147    :param cursor_position: a :class:`.Point` for the cursor position. 
    148    :param menu_position: a :class:`.Point` for the menu position. 
    149    :param show_cursor: Make the cursor visible. 
    150    """ 
    151 
    152    def __init__( 
    153        self, 
    154        get_line: Callable[[int], StyleAndTextTuples] = (lambda i: []), 
    155        line_count: int = 0, 
    156        cursor_position: Point | None = None, 
    157        menu_position: Point | None = None, 
    158        show_cursor: bool = True, 
    159    ): 
    160        self.get_line = get_line 
    161        self.line_count = line_count 
    162        self.cursor_position = cursor_position or Point(x=0, y=0) 
    163        self.menu_position = menu_position 
    164        self.show_cursor = show_cursor 
    165 
    166        # Cache for line heights. Maps cache key -> height 
    167        self._line_heights_cache: dict[Hashable, int] = {} 
    168 
    169    def __getitem__(self, lineno: int) -> StyleAndTextTuples: 
    170        "Make it iterable (iterate line by line)." 
    171        if lineno < self.line_count: 
    172            return self.get_line(lineno) 
    173        else: 
    174            raise IndexError 
    175 
    176    def get_height_for_line( 
    177        self, 
    178        lineno: int, 
    179        width: int, 
    180        get_line_prefix: GetLinePrefixCallable | None, 
    181        slice_stop: int | None = None, 
    182    ) -> int: 
    183        """ 
    184        Return the height that a given line would need if it is rendered in a 
    185        space with the given width (using line wrapping). 
    186 
    187        :param get_line_prefix: None or a `Window.get_line_prefix` callable 
    188            that returns the prefix to be inserted before this line. 
    189        :param slice_stop: Wrap only "line[:slice_stop]" and return that 
    190            partial result. This is needed for scrolling the window correctly 
    191            when line wrapping. 
    192        :returns: The computed height. 
    193        """ 
    194        # Instead of using `get_line_prefix` as key, we use render_counter 
    195        # instead. This is more reliable, because this function could still be 
    196        # the same, while the content would change over time. 
    197        key = get_app().render_counter, lineno, width, slice_stop 
    198 
    199        try: 
    200            return self._line_heights_cache[key] 
    201        except KeyError: 
    202            if width == 0: 
    203                height = 10**8 
    204            else: 
    205                # Calculate line width first. 
    206                line = fragment_list_to_text(self.get_line(lineno))[:slice_stop] 
    207                text_width = get_cwidth(line) 
    208 
    209                if get_line_prefix: 
    210                    # Add prefix width. 
    211                    text_width += fragment_list_width( 
    212                        to_formatted_text(get_line_prefix(lineno, 0)) 
    213                    ) 
    214 
    215                    # Slower path: compute path when there's a line prefix. 
    216                    height = 1 
    217 
    218                    # Keep wrapping as long as the line doesn't fit. 
    219                    # Keep adding new prefixes for every wrapped line. 
    220                    while text_width > width: 
    221                        height += 1 
    222                        text_width -= width 
    223 
    224                        fragments2 = to_formatted_text( 
    225                            get_line_prefix(lineno, height - 1) 
    226                        ) 
    227                        prefix_width = get_cwidth(fragment_list_to_text(fragments2)) 
    228 
    229                        if prefix_width >= width:  # Prefix doesn't fit. 
    230                            height = 10**8 
    231                            break 
    232 
    233                        text_width += prefix_width 
    234                else: 
    235                    # Fast path: compute height when there's no line prefix. 
    236                    try: 
    237                        quotient, remainder = divmod(text_width, width) 
    238                    except ZeroDivisionError: 
    239                        height = 10**8 
    240                    else: 
    241                        if remainder: 
    242                            quotient += 1  # Like math.ceil. 
    243                        height = max(1, quotient) 
    244 
    245            # Cache and return 
    246            self._line_heights_cache[key] = height 
    247            return height 
    248 
    249 
    250class FormattedTextControl(UIControl): 
    251    """ 
    252    Control that displays formatted text. This can be either plain text, an 
    253    :class:`~prompt_toolkit.formatted_text.HTML` object an 
    254    :class:`~prompt_toolkit.formatted_text.ANSI` object, a list of ``(style_str, 
    255    text)`` tuples or a callable that takes no argument and returns one of 
    256    those, depending on how you prefer to do the formatting. See 
    257    ``prompt_toolkit.layout.formatted_text`` for more information. 
    258 
    259    (It's mostly optimized for rather small widgets, like toolbars, menus, etc...) 
    260 
    261    When this UI control has the focus, the cursor will be shown in the upper 
    262    left corner of this control by default. There are two ways for specifying 
    263    the cursor position: 
    264 
    265    - Pass a `get_cursor_position` function which returns a `Point` instance 
    266      with the current cursor position. 
    267 
    268    - If the (formatted) text is passed as a list of ``(style, text)`` tuples 
    269      and there is one that looks like ``('[SetCursorPosition]', '')``, then 
    270      this will specify the cursor position. 
    271 
    272    Mouse support: 
    273 
    274        The list of fragments can also contain tuples of three items, looking like: 
    275        (style_str, text, handler). When mouse support is enabled and the user 
    276        clicks on this fragment, then the given handler is called. That handler 
    277        should accept two inputs: (Application, MouseEvent) and it should 
    278        either handle the event or return `NotImplemented` in case we want the 
    279        containing Window to handle this event. 
    280 
    281    :param focusable: `bool` or :class:`.Filter`: Tell whether this control is 
    282        focusable. 
    283 
    284    :param text: Text or formatted text to be displayed. 
    285    :param style: Style string applied to the content. (If you want to style 
    286        the whole :class:`~prompt_toolkit.layout.Window`, pass the style to the 
    287        :class:`~prompt_toolkit.layout.Window` instead.) 
    288    :param key_bindings: a :class:`.KeyBindings` object. 
    289    :param get_cursor_position: A callable that returns the cursor position as 
    290        a `Point` instance. 
    291    """ 
    292 
    293    def __init__( 
    294        self, 
    295        text: AnyFormattedText = "", 
    296        style: str = "", 
    297        focusable: FilterOrBool = False, 
    298        key_bindings: KeyBindingsBase | None = None, 
    299        show_cursor: bool = True, 
    300        modal: bool = False, 
    301        get_cursor_position: Callable[[], Point | None] | None = None, 
    302    ) -> None: 
    303        self.text = text  # No type check on 'text'. This is done dynamically. 
    304        self.style = style 
    305        self.focusable = to_filter(focusable) 
    306 
    307        # Key bindings. 
    308        self.key_bindings = key_bindings 
    309        self.show_cursor = show_cursor 
    310        self.modal = modal 
    311        self.get_cursor_position = get_cursor_position 
    312 
    313        #: Cache for the content. 
    314        self._content_cache: SimpleCache[Hashable, UIContent] = SimpleCache(maxsize=18) 
    315        self._fragment_cache: SimpleCache[int, StyleAndTextTuples] = SimpleCache( 
    316            maxsize=1 
    317        ) 
    318        # Only cache one fragment list. We don't need the previous item. 
    319 
    320        # Render info for the mouse support. 
    321        self._fragments: StyleAndTextTuples | None = None 
    322 
    323    def reset(self) -> None: 
    324        self._fragments = None 
    325 
    326    def is_focusable(self) -> bool: 
    327        return self.focusable() 
    328 
    329    def __repr__(self) -> str: 
    330        return f"{self.__class__.__name__}({self.text!r})" 
    331 
    332    def _get_formatted_text_cached(self) -> StyleAndTextTuples: 
    333        """ 
    334        Get fragments, but only retrieve fragments once during one render run. 
    335        (This function is called several times during one rendering, because 
    336        we also need those for calculating the dimensions.) 
    337        """ 
    338        return self._fragment_cache.get( 
    339            get_app().render_counter, lambda: to_formatted_text(self.text, self.style) 
    340        ) 
    341 
    342    def preferred_width(self, max_available_width: int) -> int: 
    343        """ 
    344        Return the preferred width for this control. 
    345        That is the width of the longest line. 
    346        """ 
    347        text = fragment_list_to_text(self._get_formatted_text_cached()) 
    348        line_lengths = [get_cwidth(l) for l in text.split("\n")] 
    349        return max(line_lengths) 
    350 
    351    def preferred_height( 
    352        self, 
    353        width: int, 
    354        max_available_height: int, 
    355        wrap_lines: bool, 
    356        get_line_prefix: GetLinePrefixCallable | None, 
    357    ) -> int | None: 
    358        """ 
    359        Return the preferred height for this control. 
    360        """ 
    361        content = self.create_content(width, None) 
    362        if wrap_lines: 
    363            height = 0 
    364            for i in range(content.line_count): 
    365                height += content.get_height_for_line(i, width, get_line_prefix) 
    366                if height >= max_available_height: 
    367                    return max_available_height 
    368            return height 
    369        else: 
    370            return content.line_count 
    371 
    372    def create_content(self, width: int, height: int | None) -> UIContent: 
    373        # Get fragments 
    374        fragments_with_mouse_handlers = self._get_formatted_text_cached() 
    375        fragment_lines_with_mouse_handlers = list( 
    376            split_lines(fragments_with_mouse_handlers) 
    377        ) 
    378 
    379        # Strip mouse handlers from fragments. 
    380        fragment_lines: list[StyleAndTextTuples] = [ 
    381            [(item[0], item[1]) for item in line] 
    382            for line in fragment_lines_with_mouse_handlers 
    383        ] 
    384 
    385        # Keep track of the fragments with mouse handler, for later use in 
    386        # `mouse_handler`. 
    387        self._fragments = fragments_with_mouse_handlers 
    388 
    389        # If there is a `[SetCursorPosition]` in the fragment list, set the 
    390        # cursor position here. 
    391        def get_cursor_position( 
    392            fragment: str = "[SetCursorPosition]", 
    393        ) -> Point | None: 
    394            for y, line in enumerate(fragment_lines): 
    395                x = 0 
    396                for style_str, text, *_ in line: 
    397                    if fragment in style_str: 
    398                        return Point(x=x, y=y) 
    399                    x += len(text) 
    400            return None 
    401 
    402        # If there is a `[SetMenuPosition]`, set the menu over here. 
    403        def get_menu_position() -> Point | None: 
    404            return get_cursor_position("[SetMenuPosition]") 
    405 
    406        cursor_position = (self.get_cursor_position or get_cursor_position)() 
    407 
    408        # Create content, or take it from the cache. 
    409        key = (tuple(fragments_with_mouse_handlers), width, cursor_position) 
    410 
    411        def get_content() -> UIContent: 
    412            return UIContent( 
    413                get_line=lambda i: fragment_lines[i], 
    414                line_count=len(fragment_lines), 
    415                show_cursor=self.show_cursor, 
    416                cursor_position=cursor_position, 
    417                menu_position=get_menu_position(), 
    418            ) 
    419 
    420        return self._content_cache.get(key, get_content) 
    421 
    422    def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: 
    423        """ 
    424        Handle mouse events. 
    425 
    426        (When the fragment list contained mouse handlers and the user clicked on 
    427        on any of these, the matching handler is called. This handler can still 
    428        return `NotImplemented` in case we want the 
    429        :class:`~prompt_toolkit.layout.Window` to handle this particular 
    430        event.) 
    431        """ 
    432        if self._fragments: 
    433            # Read the generator. 
    434            fragments_for_line = list(split_lines(self._fragments)) 
    435 
    436            try: 
    437                fragments = fragments_for_line[mouse_event.position.y] 
    438            except IndexError: 
    439                return NotImplemented 
    440            else: 
    441                # Find position in the fragment list. 
    442                xpos = mouse_event.position.x 
    443 
    444                # Find mouse handler for this character. 
    445                count = 0 
    446                for item in fragments: 
    447                    count += len(item[1]) 
    448                    if count > xpos: 
    449                        if len(item) >= 3: 
    450                            # Handler found. Call it. 
    451                            # (Handler can return NotImplemented, so return 
    452                            # that result.) 
    453                            handler = item[2] 
    454                            return handler(mouse_event) 
    455                        else: 
    456                            break 
    457 
    458        # Otherwise, don't handle here. 
    459        return NotImplemented 
    460 
    461    def is_modal(self) -> bool: 
    462        return self.modal 
    463 
    464    def get_key_bindings(self) -> KeyBindingsBase | None: 
    465        return self.key_bindings 
    466 
    467 
    468class DummyControl(UIControl): 
    469    """ 
    470    A dummy control object that doesn't paint any content. 
    471 
    472    Useful for filling a :class:`~prompt_toolkit.layout.Window`. (The 
    473    `fragment` and `char` attributes of the `Window` class can be used to 
    474    define the filling.) 
    475    """ 
    476 
    477    def create_content(self, width: int, height: int) -> UIContent: 
    478        def get_line(i: int) -> StyleAndTextTuples: 
    479            return [] 
    480 
    481        return UIContent(get_line=get_line, line_count=100**100)  # Something very big. 
    482 
    483    def is_focusable(self) -> bool: 
    484        return False 
    485 
    486 
    487class _ProcessedLine(NamedTuple): 
    488    fragments: StyleAndTextTuples 
    489    source_to_display: Callable[[int], int] 
    490    display_to_source: Callable[[int], int] 
    491 
    492 
    493class BufferControl(UIControl): 
    494    """ 
    495    Control for visualizing the content of a :class:`.Buffer`. 
    496 
    497    :param buffer: The :class:`.Buffer` object to be displayed. 
    498    :param input_processors: A list of 
    499        :class:`~prompt_toolkit.layout.processors.Processor` objects. 
    500    :param include_default_input_processors: When True, include the default 
    501        processors for highlighting of selection, search and displaying of 
    502        multiple cursors. 
    503    :param lexer: :class:`.Lexer` instance for syntax highlighting. 
    504    :param preview_search: `bool` or :class:`.Filter`: Show search while 
    505        typing. When this is `True`, probably you want to add a 
    506        ``HighlightIncrementalSearchProcessor`` as well. Otherwise only the 
    507        cursor position will move, but the text won't be highlighted. 
    508    :param focusable: `bool` or :class:`.Filter`: Tell whether this control is focusable. 
    509    :param focus_on_click: Focus this buffer when it's click, but not yet focused. 
    510    :param key_bindings: a :class:`.KeyBindings` object. 
    511    """ 
    512 
    513    def __init__( 
    514        self, 
    515        buffer: Buffer | None = None, 
    516        input_processors: list[Processor] | None = None, 
    517        include_default_input_processors: bool = True, 
    518        lexer: Lexer | None = None, 
    519        preview_search: FilterOrBool = False, 
    520        focusable: FilterOrBool = True, 
    521        search_buffer_control: ( 
    522            None | SearchBufferControl | Callable[[], SearchBufferControl] 
    523        ) = None, 
    524        menu_position: Callable[[], int | None] | None = None, 
    525        focus_on_click: FilterOrBool = False, 
    526        key_bindings: KeyBindingsBase | None = None, 
    527    ): 
    528        self.input_processors = input_processors 
    529        self.include_default_input_processors = include_default_input_processors 
    530 
    531        self.default_input_processors = [ 
    532            HighlightSearchProcessor(), 
    533            HighlightIncrementalSearchProcessor(), 
    534            HighlightSelectionProcessor(), 
    535            DisplayMultipleCursors(), 
    536        ] 
    537 
    538        self.preview_search = to_filter(preview_search) 
    539        self.focusable = to_filter(focusable) 
    540        self.focus_on_click = to_filter(focus_on_click) 
    541 
    542        self.buffer = buffer or Buffer() 
    543        self.menu_position = menu_position 
    544        self.lexer = lexer or SimpleLexer() 
    545        self.key_bindings = key_bindings 
    546        self._search_buffer_control = search_buffer_control 
    547 
    548        #: Cache for the lexer. 
    549        #: Often, due to cursor movement, undo/redo and window resizing 
    550        #: operations, it happens that a short time, the same document has to be 
    551        #: lexed. This is a fairly easy way to cache such an expensive operation. 
    552        self._fragment_cache: SimpleCache[ 
    553            Hashable, Callable[[int], StyleAndTextTuples] 
    554        ] = SimpleCache(maxsize=8) 
    555 
    556        self._last_click_timestamp: float | None = None 
    557        self._last_get_processed_line: Callable[[int], _ProcessedLine] | None = None 
    558 
    559    def __repr__(self) -> str: 
    560        return f"<{self.__class__.__name__} buffer={self.buffer!r} at {id(self)!r}>" 
    561 
    562    @property 
    563    def search_buffer_control(self) -> SearchBufferControl | None: 
    564        result: SearchBufferControl | None 
    565 
    566        if callable(self._search_buffer_control): 
    567            result = self._search_buffer_control() 
    568        else: 
    569            result = self._search_buffer_control 
    570 
    571        assert result is None or isinstance(result, SearchBufferControl) 
    572        return result 
    573 
    574    @property 
    575    def search_buffer(self) -> Buffer | None: 
    576        control = self.search_buffer_control 
    577        if control is not None: 
    578            return control.buffer 
    579        return None 
    580 
    581    @property 
    582    def search_state(self) -> SearchState: 
    583        """ 
    584        Return the `SearchState` for searching this `BufferControl`. This is 
    585        always associated with the search control. If one search bar is used 
    586        for searching multiple `BufferControls`, then they share the same 
    587        `SearchState`. 
    588        """ 
    589        search_buffer_control = self.search_buffer_control 
    590        if search_buffer_control: 
    591            return search_buffer_control.searcher_search_state 
    592        else: 
    593            return SearchState() 
    594 
    595    def is_focusable(self) -> bool: 
    596        return self.focusable() 
    597 
    598    def preferred_width(self, max_available_width: int) -> int | None: 
    599        """ 
    600        This should return the preferred width. 
    601 
    602        Note: We don't specify a preferred width according to the content, 
    603              because it would be too expensive. Calculating the preferred 
    604              width can be done by calculating the longest line, but this would 
    605              require applying all the processors to each line. This is 
    606              unfeasible for a larger document, and doing it for small 
    607              documents only would result in inconsistent behavior. 
    608        """ 
    609        return None 
    610 
    611    def preferred_height( 
    612        self, 
    613        width: int, 
    614        max_available_height: int, 
    615        wrap_lines: bool, 
    616        get_line_prefix: GetLinePrefixCallable | None, 
    617    ) -> int | None: 
    618        # Calculate the content height, if it was drawn on a screen with the 
    619        # given width. 
    620        height = 0 
    621        content = self.create_content(width, height=1)  # Pass a dummy '1' as height. 
    622 
    623        # When line wrapping is off, the height should be equal to the amount 
    624        # of lines. 
    625        if not wrap_lines: 
    626            return content.line_count 
    627 
    628        # When the number of lines exceeds the max_available_height, just 
    629        # return max_available_height. No need to calculate anything. 
    630        if content.line_count >= max_available_height: 
    631            return max_available_height 
    632 
    633        for i in range(content.line_count): 
    634            height += content.get_height_for_line(i, width, get_line_prefix) 
    635 
    636            if height >= max_available_height: 
    637                return max_available_height 
    638 
    639        return height 
    640 
    641    def _get_formatted_text_for_line_func( 
    642        self, document: Document 
    643    ) -> Callable[[int], StyleAndTextTuples]: 
    644        """ 
    645        Create a function that returns the fragments for a given line. 
    646        """ 
    647 
    648        # Cache using `document.text`. 
    649        def get_formatted_text_for_line() -> Callable[[int], StyleAndTextTuples]: 
    650            return self.lexer.lex_document(document) 
    651 
    652        key = (document.text, self.lexer.invalidation_hash()) 
    653        return self._fragment_cache.get(key, get_formatted_text_for_line) 
    654 
    655    def _create_get_processed_line_func( 
    656        self, document: Document, width: int, height: int 
    657    ) -> Callable[[int], _ProcessedLine]: 
    658        """ 
    659        Create a function that takes a line number of the current document and 
    660        returns a _ProcessedLine(processed_fragments, source_to_display, display_to_source) 
    661        tuple. 
    662        """ 
    663        # Merge all input processors together. 
    664        input_processors = self.input_processors or [] 
    665        if self.include_default_input_processors: 
    666            input_processors = self.default_input_processors + input_processors 
    667 
    668        merged_processor = merge_processors(input_processors) 
    669 
    670        def transform( 
    671            lineno: int, 
    672            fragments: StyleAndTextTuples, 
    673            get_line: Callable[[int], StyleAndTextTuples], 
    674        ) -> _ProcessedLine: 
    675            "Transform the fragments for a given line number." 
    676 
    677            # Get cursor position at this line. 
    678            def source_to_display(i: int) -> int: 
    679                """X position from the buffer to the x position in the 
    680                processed fragment list. By default, we start from the 'identity' 
    681                operation.""" 
    682                return i 
    683 
    684            transformation = merged_processor.apply_transformation( 
    685                TransformationInput( 
    686                    self, 
    687                    document, 
    688                    lineno, 
    689                    source_to_display, 
    690                    fragments, 
    691                    width, 
    692                    height, 
    693                    get_line, 
    694                ) 
    695            ) 
    696 
    697            return _ProcessedLine( 
    698                transformation.fragments, 
    699                transformation.source_to_display, 
    700                transformation.display_to_source, 
    701            ) 
    702 
    703        def create_func() -> Callable[[int], _ProcessedLine]: 
    704            get_line = self._get_formatted_text_for_line_func(document) 
    705            cache: dict[int, _ProcessedLine] = {} 
    706 
    707            def get_processed_line(i: int) -> _ProcessedLine: 
    708                try: 
    709                    return cache[i] 
    710                except KeyError: 
    711                    processed_line = transform(i, get_line(i), get_line) 
    712                    cache[i] = processed_line 
    713                    return processed_line 
    714 
    715            return get_processed_line 
    716 
    717        return create_func() 
    718 
    719    def create_content( 
    720        self, width: int, height: int, preview_search: bool = False 
    721    ) -> UIContent: 
    722        """ 
    723        Create a UIContent. 
    724        """ 
    725        buffer = self.buffer 
    726 
    727        # Trigger history loading of the buffer. We do this during the 
    728        # rendering of the UI here, because it needs to happen when an 
    729        # `Application` with its event loop is running. During the rendering of 
    730        # the buffer control is the earliest place we can achieve this, where 
    731        # we're sure the right event loop is active, and don't require user 
    732        # interaction (like in a key binding). 
    733        buffer.load_history_if_not_yet_loaded() 
    734 
    735        # Get the document to be shown. If we are currently searching (the 
    736        # search buffer has focus, and the preview_search filter is enabled), 
    737        # then use the search document, which has possibly a different 
    738        # text/cursor position.) 
    739        search_control = self.search_buffer_control 
    740        preview_now = preview_search or bool( 
    741            # Only if this feature is enabled. 
    742            self.preview_search() 
    743            and 
    744            # And something was typed in the associated search field. 
    745            search_control 
    746            and search_control.buffer.text 
    747            and 
    748            # And we are searching in this control. (Many controls can point to 
    749            # the same search field, like in Pyvim.) 
    750            get_app().layout.search_target_buffer_control == self 
    751        ) 
    752 
    753        if preview_now and search_control is not None: 
    754            ss = self.search_state 
    755 
    756            document = buffer.document_for_search( 
    757                SearchState( 
    758                    text=search_control.buffer.text, 
    759                    direction=ss.direction, 
    760                    ignore_case=ss.ignore_case, 
    761                ) 
    762            ) 
    763        else: 
    764            document = buffer.document 
    765 
    766        get_processed_line = self._create_get_processed_line_func( 
    767            document, width, height 
    768        ) 
    769        self._last_get_processed_line = get_processed_line 
    770 
    771        def translate_rowcol(row: int, col: int) -> Point: 
    772            "Return the content column for this coordinate." 
    773            return Point(x=get_processed_line(row).source_to_display(col), y=row) 
    774 
    775        def get_line(i: int) -> StyleAndTextTuples: 
    776            "Return the fragments for a given line number." 
    777            fragments = get_processed_line(i).fragments 
    778 
    779            # Add a space at the end, because that is a possible cursor 
    780            # position. (When inserting after the input.) We should do this on 
    781            # all the lines, not just the line containing the cursor. (Because 
    782            # otherwise, line wrapping/scrolling could change when moving the 
    783            # cursor around.) 
    784            fragments = fragments + [("", " ")] 
    785            return fragments 
    786 
    787        content = UIContent( 
    788            get_line=get_line, 
    789            line_count=document.line_count, 
    790            cursor_position=translate_rowcol( 
    791                document.cursor_position_row, document.cursor_position_col 
    792            ), 
    793        ) 
    794 
    795        # If there is an auto completion going on, use that start point for a 
    796        # pop-up menu position. (But only when this buffer has the focus -- 
    797        # there is only one place for a menu, determined by the focused buffer.) 
    798        if get_app().layout.current_control == self: 
    799            menu_position = self.menu_position() if self.menu_position else None 
    800            if menu_position is not None: 
    801                assert isinstance(menu_position, int) 
    802                menu_row, menu_col = buffer.document.translate_index_to_position( 
    803                    menu_position 
    804                ) 
    805                content.menu_position = translate_rowcol(menu_row, menu_col) 
    806            elif buffer.complete_state: 
    807                # Position for completion menu. 
    808                # Note: We use 'min', because the original cursor position could be 
    809                #       behind the input string when the actual completion is for 
    810                #       some reason shorter than the text we had before. (A completion 
    811                #       can change and shorten the input.) 
    812                menu_row, menu_col = buffer.document.translate_index_to_position( 
    813                    min( 
    814                        buffer.cursor_position, 
    815                        buffer.complete_state.original_document.cursor_position, 
    816                    ) 
    817                ) 
    818                content.menu_position = translate_rowcol(menu_row, menu_col) 
    819            else: 
    820                content.menu_position = None 
    821 
    822        return content 
    823 
    824    def mouse_handler(self, mouse_event: MouseEvent) -> NotImplementedOrNone: 
    825        """ 
    826        Mouse handler for this control. 
    827        """ 
    828        buffer = self.buffer 
    829        position = mouse_event.position 
    830 
    831        # Focus buffer when clicked. 
    832        if get_app().layout.current_control == self: 
    833            if self._last_get_processed_line: 
    834                processed_line = self._last_get_processed_line(position.y) 
    835 
    836                # Translate coordinates back to the cursor position of the 
    837                # original input. 
    838                xpos = processed_line.display_to_source(position.x) 
    839                index = buffer.document.translate_row_col_to_index(position.y, xpos) 
    840 
    841                # Set the cursor position. 
    842                if mouse_event.event_type == MouseEventType.MOUSE_DOWN: 
    843                    buffer.exit_selection() 
    844                    buffer.cursor_position = index 
    845 
    846                elif ( 
    847                    mouse_event.event_type == MouseEventType.MOUSE_MOVE 
    848                    and mouse_event.button != MouseButton.NONE 
    849                ): 
    850                    # Click and drag to highlight a selection 
    851                    if ( 
    852                        buffer.selection_state is None 
    853                        and abs(buffer.cursor_position - index) > 0 
    854                    ): 
    855                        buffer.start_selection(selection_type=SelectionType.CHARACTERS) 
    856                    buffer.cursor_position = index 
    857 
    858                elif mouse_event.event_type == MouseEventType.MOUSE_UP: 
    859                    # When the cursor was moved to another place, select the text. 
    860                    # (The >1 is actually a small but acceptable workaround for 
    861                    # selecting text in Vi navigation mode. In navigation mode, 
    862                    # the cursor can never be after the text, so the cursor 
    863                    # will be repositioned automatically.) 
    864                    if abs(buffer.cursor_position - index) > 1: 
    865                        if buffer.selection_state is None: 
    866                            buffer.start_selection( 
    867                                selection_type=SelectionType.CHARACTERS 
    868                            ) 
    869                        buffer.cursor_position = index 
    870 
    871                    # Select word around cursor on double click. 
    872                    # Two MOUSE_UP events in a short timespan are considered a double click. 
    873                    double_click = ( 
    874                        self._last_click_timestamp 
    875                        and time.time() - self._last_click_timestamp < 0.3 
    876                    ) 
    877                    self._last_click_timestamp = time.time() 
    878 
    879                    if double_click: 
    880                        start, end = buffer.document.find_boundaries_of_current_word() 
    881                        buffer.cursor_position += start 
    882                        buffer.start_selection(selection_type=SelectionType.CHARACTERS) 
    883                        buffer.cursor_position += end - start 
    884                else: 
    885                    # Don't handle scroll events here. 
    886                    return NotImplemented 
    887 
    888        # Not focused, but focusing on click events. 
    889        else: 
    890            if ( 
    891                self.focus_on_click() 
    892                and mouse_event.event_type == MouseEventType.MOUSE_UP 
    893            ): 
    894                # Focus happens on mouseup. (If we did this on mousedown, the 
    895                # up event will be received at the point where this widget is 
    896                # focused and be handled anyway.) 
    897                get_app().layout.current_control = self 
    898            else: 
    899                return NotImplemented 
    900 
    901        return None 
    902 
    903    def move_cursor_down(self) -> None: 
    904        b = self.buffer 
    905        b.cursor_position += b.document.get_cursor_down_position() 
    906 
    907    def move_cursor_up(self) -> None: 
    908        b = self.buffer 
    909        b.cursor_position += b.document.get_cursor_up_position() 
    910 
    911    def get_key_bindings(self) -> KeyBindingsBase | None: 
    912        """ 
    913        When additional key bindings are given. Return these. 
    914        """ 
    915        return self.key_bindings 
    916 
    917    def get_invalidate_events(self) -> Iterable[Event[object]]: 
    918        """ 
    919        Return the Window invalidate events. 
    920        """ 
    921        # Whenever the buffer changes, the UI has to be updated. 
    922        yield self.buffer.on_text_changed 
    923        yield self.buffer.on_cursor_position_changed 
    924 
    925        yield self.buffer.on_completions_changed 
    926        yield self.buffer.on_suggestion_set 
    927 
    928 
    929class SearchBufferControl(BufferControl): 
    930    """ 
    931    :class:`.BufferControl` which is used for searching another 
    932    :class:`.BufferControl`. 
    933 
    934    :param ignore_case: Search case insensitive. 
    935    """ 
    936 
    937    def __init__( 
    938        self, 
    939        buffer: Buffer | None = None, 
    940        input_processors: list[Processor] | None = None, 
    941        lexer: Lexer | None = None, 
    942        focus_on_click: FilterOrBool = False, 
    943        key_bindings: KeyBindingsBase | None = None, 
    944        ignore_case: FilterOrBool = False, 
    945    ): 
    946        super().__init__( 
    947            buffer=buffer, 
    948            input_processors=input_processors, 
    949            lexer=lexer, 
    950            focus_on_click=focus_on_click, 
    951            key_bindings=key_bindings, 
    952        ) 
    953 
    954        # If this BufferControl is used as a search field for one or more other 
    955        # BufferControls, then represents the search state. 
    956        self.searcher_search_state = SearchState(ignore_case=ignore_case)