1""" 
    2Search operations. 
    3 
    4For the key bindings implementation with attached filters, check 
    5`prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings 
    6instead of calling these function directly.) 
    7""" 
    8 
    9from __future__ import annotations 
    10 
    11from enum import Enum 
    12from typing import TYPE_CHECKING 
    13 
    14from .application.current import get_app 
    15from .filters import FilterOrBool, is_searching, to_filter 
    16from .key_binding.vi_state import InputMode 
    17 
    18if TYPE_CHECKING: 
    19    from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl 
    20    from prompt_toolkit.layout.layout import Layout 
    21 
    22__all__ = [ 
    23    "SearchDirection", 
    24    "start_search", 
    25    "stop_search", 
    26] 
    27 
    28 
    29class SearchDirection(Enum): 
    30    FORWARD = "FORWARD" 
    31    BACKWARD = "BACKWARD" 
    32 
    33 
    34class SearchState: 
    35    """ 
    36    A search 'query', associated with a search field (like a SearchToolbar). 
    37 
    38    Every searchable `BufferControl` points to a `search_buffer_control` 
    39    (another `BufferControls`) which represents the search field. The 
    40    `SearchState` attached to that search field is used for storing the current 
    41    search query. 
    42 
    43    It is possible to have one searchfield for multiple `BufferControls`. In 
    44    that case, they'll share the same `SearchState`. 
    45    If there are multiple `BufferControls` that display the same `Buffer`, then 
    46    they can have a different `SearchState` each (if they have a different 
    47    search control). 
    48    """ 
    49 
    50    __slots__ = ("text", "direction", "ignore_case") 
    51 
    52    def __init__( 
    53        self, 
    54        text: str = "", 
    55        direction: SearchDirection = SearchDirection.FORWARD, 
    56        ignore_case: FilterOrBool = False, 
    57    ) -> None: 
    58        self.text = text 
    59        self.direction = direction 
    60        self.ignore_case = to_filter(ignore_case) 
    61 
    62    def __repr__(self) -> str: 
    63        return f"{self.__class__.__name__}({self.text!r}, direction={self.direction!r}, ignore_case={self.ignore_case!r})" 
    64 
    65    def __invert__(self) -> SearchState: 
    66        """ 
    67        Create a new SearchState where backwards becomes forwards and the other 
    68        way around. 
    69        """ 
    70        if self.direction == SearchDirection.BACKWARD: 
    71            direction = SearchDirection.FORWARD 
    72        else: 
    73            direction = SearchDirection.BACKWARD 
    74 
    75        return SearchState( 
    76            text=self.text, direction=direction, ignore_case=self.ignore_case 
    77        ) 
    78 
    79 
    80def start_search( 
    81    buffer_control: BufferControl | None = None, 
    82    direction: SearchDirection = SearchDirection.FORWARD, 
    83) -> None: 
    84    """ 
    85    Start search through the given `buffer_control` using the 
    86    `search_buffer_control`. 
    87 
    88    :param buffer_control: Start search for this `BufferControl`. If not given, 
    89        search through the current control. 
    90    """ 
    91    from prompt_toolkit.layout.controls import BufferControl 
    92 
    93    assert buffer_control is None or isinstance(buffer_control, BufferControl) 
    94 
    95    layout = get_app().layout 
    96 
    97    # When no control is given, use the current control if that's a BufferControl. 
    98    if buffer_control is None: 
    99        if not isinstance(layout.current_control, BufferControl): 
    100            return 
    101        buffer_control = layout.current_control 
    102 
    103    # Only if this control is searchable. 
    104    search_buffer_control = buffer_control.search_buffer_control 
    105 
    106    if search_buffer_control: 
    107        buffer_control.search_state.direction = direction 
    108 
    109        # Make sure to focus the search BufferControl 
    110        layout.focus(search_buffer_control) 
    111 
    112        # Remember search link. 
    113        layout.search_links[search_buffer_control] = buffer_control 
    114 
    115        # If we're in Vi mode, make sure to go into insert mode. 
    116        get_app().vi_state.input_mode = InputMode.INSERT 
    117 
    118 
    119def stop_search(buffer_control: BufferControl | None = None) -> None: 
    120    """ 
    121    Stop search through the given `buffer_control`. 
    122    """ 
    123    layout = get_app().layout 
    124 
    125    if buffer_control is None: 
    126        buffer_control = layout.search_target_buffer_control 
    127        if buffer_control is None: 
    128            # (Should not happen, but possible when `stop_search` is called 
    129            # when we're not searching.) 
    130            return 
    131        search_buffer_control = buffer_control.search_buffer_control 
    132    else: 
    133        assert buffer_control in layout.search_links.values() 
    134        search_buffer_control = _get_reverse_search_links(layout)[buffer_control] 
    135 
    136    # Focus the original buffer again. 
    137    layout.focus(buffer_control) 
    138 
    139    if search_buffer_control is not None: 
    140        # Remove the search link. 
    141        del layout.search_links[search_buffer_control] 
    142 
    143        # Reset content of search control. 
    144        search_buffer_control.buffer.reset() 
    145 
    146    # If we're in Vi mode, go back to navigation mode. 
    147    get_app().vi_state.input_mode = InputMode.NAVIGATION 
    148 
    149 
    150def do_incremental_search(direction: SearchDirection, count: int = 1) -> None: 
    151    """ 
    152    Apply search, but keep search buffer focused. 
    153    """ 
    154    assert is_searching() 
    155 
    156    layout = get_app().layout 
    157 
    158    # Only search if the current control is a `BufferControl`. 
    159    from prompt_toolkit.layout.controls import BufferControl 
    160 
    161    search_control = layout.current_control 
    162    if not isinstance(search_control, BufferControl): 
    163        return 
    164 
    165    prev_control = layout.search_target_buffer_control 
    166    if prev_control is None: 
    167        return 
    168    search_state = prev_control.search_state 
    169 
    170    # Update search_state. 
    171    direction_changed = search_state.direction != direction 
    172 
    173    search_state.text = search_control.buffer.text 
    174    search_state.direction = direction 
    175 
    176    # Apply search to current buffer. 
    177    if not direction_changed: 
    178        prev_control.buffer.apply_search( 
    179            search_state, include_current_position=False, count=count 
    180        ) 
    181 
    182 
    183def accept_search() -> None: 
    184    """ 
    185    Accept current search query. Focus original `BufferControl` again. 
    186    """ 
    187    layout = get_app().layout 
    188 
    189    search_control = layout.current_control 
    190    target_buffer_control = layout.search_target_buffer_control 
    191 
    192    from prompt_toolkit.layout.controls import BufferControl 
    193 
    194    if not isinstance(search_control, BufferControl): 
    195        return 
    196    if target_buffer_control is None: 
    197        return 
    198 
    199    search_state = target_buffer_control.search_state 
    200 
    201    # Update search state. 
    202    if search_control.buffer.text: 
    203        search_state.text = search_control.buffer.text 
    204 
    205    # Apply search. 
    206    target_buffer_control.buffer.apply_search( 
    207        search_state, include_current_position=True 
    208    ) 
    209 
    210    # Add query to history of search line. 
    211    search_control.buffer.append_to_history() 
    212 
    213    # Stop search and focus previous control again. 
    214    stop_search(target_buffer_control) 
    215 
    216 
    217def _get_reverse_search_links( 
    218    layout: Layout, 
    219) -> dict[BufferControl, SearchBufferControl]: 
    220    """ 
    221    Return mapping from BufferControl to SearchBufferControl. 
    222    """ 
    223    return { 
    224        buffer_control: search_buffer_control 
    225        for search_buffer_control, buffer_control in layout.search_links.items() 
    226    }