| """ |
| Search operations. |
| |
| For the key bindings implementation with attached filters, check |
| `prompt_toolkit.key_binding.bindings.search`. (Use these for new key bindings |
| instead of calling these function directly.) |
| """ |
| from __future__ import annotations |
|
|
| from enum import Enum |
| from typing import TYPE_CHECKING |
|
|
| from .application.current import get_app |
| from .filters import FilterOrBool, is_searching, to_filter |
| from .key_binding.vi_state import InputMode |
|
|
| if TYPE_CHECKING: |
| from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl |
| from prompt_toolkit.layout.layout import Layout |
|
|
| __all__ = [ |
| "SearchDirection", |
| "start_search", |
| "stop_search", |
| ] |
|
|
|
|
| class SearchDirection(Enum): |
| FORWARD = "FORWARD" |
| BACKWARD = "BACKWARD" |
|
|
|
|
| class SearchState: |
| """ |
| A search 'query', associated with a search field (like a SearchToolbar). |
| |
| Every searchable `BufferControl` points to a `search_buffer_control` |
| (another `BufferControls`) which represents the search field. The |
| `SearchState` attached to that search field is used for storing the current |
| search query. |
| |
| It is possible to have one searchfield for multiple `BufferControls`. In |
| that case, they'll share the same `SearchState`. |
| If there are multiple `BufferControls` that display the same `Buffer`, then |
| they can have a different `SearchState` each (if they have a different |
| search control). |
| """ |
|
|
| __slots__ = ("text", "direction", "ignore_case") |
|
|
| def __init__( |
| self, |
| text: str = "", |
| direction: SearchDirection = SearchDirection.FORWARD, |
| ignore_case: FilterOrBool = False, |
| ) -> None: |
| self.text = text |
| self.direction = direction |
| self.ignore_case = to_filter(ignore_case) |
|
|
| def __repr__(self) -> str: |
| return "{}({!r}, direction={!r}, ignore_case={!r})".format( |
| self.__class__.__name__, |
| self.text, |
| self.direction, |
| self.ignore_case, |
| ) |
|
|
| def __invert__(self) -> SearchState: |
| """ |
| Create a new SearchState where backwards becomes forwards and the other |
| way around. |
| """ |
| if self.direction == SearchDirection.BACKWARD: |
| direction = SearchDirection.FORWARD |
| else: |
| direction = SearchDirection.BACKWARD |
|
|
| return SearchState( |
| text=self.text, direction=direction, ignore_case=self.ignore_case |
| ) |
|
|
|
|
| def start_search( |
| buffer_control: BufferControl | None = None, |
| direction: SearchDirection = SearchDirection.FORWARD, |
| ) -> None: |
| """ |
| Start search through the given `buffer_control` using the |
| `search_buffer_control`. |
| |
| :param buffer_control: Start search for this `BufferControl`. If not given, |
| search through the current control. |
| """ |
| from prompt_toolkit.layout.controls import BufferControl |
|
|
| assert buffer_control is None or isinstance(buffer_control, BufferControl) |
|
|
| layout = get_app().layout |
|
|
| |
| if buffer_control is None: |
| if not isinstance(layout.current_control, BufferControl): |
| return |
| buffer_control = layout.current_control |
|
|
| |
| search_buffer_control = buffer_control.search_buffer_control |
|
|
| if search_buffer_control: |
| buffer_control.search_state.direction = direction |
|
|
| |
| layout.focus(search_buffer_control) |
|
|
| |
| layout.search_links[search_buffer_control] = buffer_control |
|
|
| |
| get_app().vi_state.input_mode = InputMode.INSERT |
|
|
|
|
| def stop_search(buffer_control: BufferControl | None = None) -> None: |
| """ |
| Stop search through the given `buffer_control`. |
| """ |
| layout = get_app().layout |
|
|
| if buffer_control is None: |
| buffer_control = layout.search_target_buffer_control |
| if buffer_control is None: |
| |
| |
| return |
| search_buffer_control = buffer_control.search_buffer_control |
| else: |
| assert buffer_control in layout.search_links.values() |
| search_buffer_control = _get_reverse_search_links(layout)[buffer_control] |
|
|
| |
| layout.focus(buffer_control) |
|
|
| if search_buffer_control is not None: |
| |
| del layout.search_links[search_buffer_control] |
|
|
| |
| search_buffer_control.buffer.reset() |
|
|
| |
| get_app().vi_state.input_mode = InputMode.NAVIGATION |
|
|
|
|
| def do_incremental_search(direction: SearchDirection, count: int = 1) -> None: |
| """ |
| Apply search, but keep search buffer focused. |
| """ |
| assert is_searching() |
|
|
| layout = get_app().layout |
|
|
| |
| from prompt_toolkit.layout.controls import BufferControl |
|
|
| search_control = layout.current_control |
| if not isinstance(search_control, BufferControl): |
| return |
|
|
| prev_control = layout.search_target_buffer_control |
| if prev_control is None: |
| return |
| search_state = prev_control.search_state |
|
|
| |
| direction_changed = search_state.direction != direction |
|
|
| search_state.text = search_control.buffer.text |
| search_state.direction = direction |
|
|
| |
| if not direction_changed: |
| prev_control.buffer.apply_search( |
| search_state, include_current_position=False, count=count |
| ) |
|
|
|
|
| def accept_search() -> None: |
| """ |
| Accept current search query. Focus original `BufferControl` again. |
| """ |
| layout = get_app().layout |
|
|
| search_control = layout.current_control |
| target_buffer_control = layout.search_target_buffer_control |
|
|
| from prompt_toolkit.layout.controls import BufferControl |
|
|
| if not isinstance(search_control, BufferControl): |
| return |
| if target_buffer_control is None: |
| return |
|
|
| search_state = target_buffer_control.search_state |
|
|
| |
| if search_control.buffer.text: |
| search_state.text = search_control.buffer.text |
|
|
| |
| target_buffer_control.buffer.apply_search( |
| search_state, include_current_position=True |
| ) |
|
|
| |
| search_control.buffer.append_to_history() |
|
|
| |
| stop_search(target_buffer_control) |
|
|
|
|
| def _get_reverse_search_links( |
| layout: Layout, |
| ) -> dict[BufferControl, SearchBufferControl]: |
| """ |
| Return mapping from BufferControl to SearchBufferControl. |
| """ |
| return { |
| buffer_control: search_buffer_control |
| for search_buffer_control, buffer_control in layout.search_links.items() |
| } |
|
|