Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/prompt_toolkit/search.py: 24%
87 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-20 06:09 +0000
1"""
2Search operations.
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"""
8from __future__ import annotations
10from enum import Enum
11from typing import TYPE_CHECKING
13from .application.current import get_app
14from .filters import FilterOrBool, is_searching, to_filter
15from .key_binding.vi_state import InputMode
17if TYPE_CHECKING:
18 from prompt_toolkit.layout.controls import BufferControl, SearchBufferControl
19 from prompt_toolkit.layout.layout import Layout
21__all__ = [
22 "SearchDirection",
23 "start_search",
24 "stop_search",
25]
28class SearchDirection(Enum):
29 FORWARD = "FORWARD"
30 BACKWARD = "BACKWARD"
33class SearchState:
34 """
35 A search 'query', associated with a search field (like a SearchToolbar).
37 Every searchable `BufferControl` points to a `search_buffer_control`
38 (another `BufferControls`) which represents the search field. The
39 `SearchState` attached to that search field is used for storing the current
40 search query.
42 It is possible to have one searchfield for multiple `BufferControls`. In
43 that case, they'll share the same `SearchState`.
44 If there are multiple `BufferControls` that display the same `Buffer`, then
45 they can have a different `SearchState` each (if they have a different
46 search control).
47 """
49 __slots__ = ("text", "direction", "ignore_case")
51 def __init__(
52 self,
53 text: str = "",
54 direction: SearchDirection = SearchDirection.FORWARD,
55 ignore_case: FilterOrBool = False,
56 ) -> None:
57 self.text = text
58 self.direction = direction
59 self.ignore_case = to_filter(ignore_case)
61 def __repr__(self) -> str:
62 return "{}({!r}, direction={!r}, ignore_case={!r})".format(
63 self.__class__.__name__,
64 self.text,
65 self.direction,
66 self.ignore_case,
67 )
69 def __invert__(self) -> SearchState:
70 """
71 Create a new SearchState where backwards becomes forwards and the other
72 way around.
73 """
74 if self.direction == SearchDirection.BACKWARD:
75 direction = SearchDirection.FORWARD
76 else:
77 direction = SearchDirection.BACKWARD
79 return SearchState(
80 text=self.text, direction=direction, ignore_case=self.ignore_case
81 )
84def start_search(
85 buffer_control: BufferControl | None = None,
86 direction: SearchDirection = SearchDirection.FORWARD,
87) -> None:
88 """
89 Start search through the given `buffer_control` using the
90 `search_buffer_control`.
92 :param buffer_control: Start search for this `BufferControl`. If not given,
93 search through the current control.
94 """
95 from prompt_toolkit.layout.controls import BufferControl
97 assert buffer_control is None or isinstance(buffer_control, BufferControl)
99 layout = get_app().layout
101 # When no control is given, use the current control if that's a BufferControl.
102 if buffer_control is None:
103 if not isinstance(layout.current_control, BufferControl):
104 return
105 buffer_control = layout.current_control
107 # Only if this control is searchable.
108 search_buffer_control = buffer_control.search_buffer_control
110 if search_buffer_control:
111 buffer_control.search_state.direction = direction
113 # Make sure to focus the search BufferControl
114 layout.focus(search_buffer_control)
116 # Remember search link.
117 layout.search_links[search_buffer_control] = buffer_control
119 # If we're in Vi mode, make sure to go into insert mode.
120 get_app().vi_state.input_mode = InputMode.INSERT
123def stop_search(buffer_control: BufferControl | None = None) -> None:
124 """
125 Stop search through the given `buffer_control`.
126 """
127 layout = get_app().layout
129 if buffer_control is None:
130 buffer_control = layout.search_target_buffer_control
131 if buffer_control is None:
132 # (Should not happen, but possible when `stop_search` is called
133 # when we're not searching.)
134 return
135 search_buffer_control = buffer_control.search_buffer_control
136 else:
137 assert buffer_control in layout.search_links.values()
138 search_buffer_control = _get_reverse_search_links(layout)[buffer_control]
140 # Focus the original buffer again.
141 layout.focus(buffer_control)
143 if search_buffer_control is not None:
144 # Remove the search link.
145 del layout.search_links[search_buffer_control]
147 # Reset content of search control.
148 search_buffer_control.buffer.reset()
150 # If we're in Vi mode, go back to navigation mode.
151 get_app().vi_state.input_mode = InputMode.NAVIGATION
154def do_incremental_search(direction: SearchDirection, count: int = 1) -> None:
155 """
156 Apply search, but keep search buffer focused.
157 """
158 assert is_searching()
160 layout = get_app().layout
162 # Only search if the current control is a `BufferControl`.
163 from prompt_toolkit.layout.controls import BufferControl
165 search_control = layout.current_control
166 if not isinstance(search_control, BufferControl):
167 return
169 prev_control = layout.search_target_buffer_control
170 if prev_control is None:
171 return
172 search_state = prev_control.search_state
174 # Update search_state.
175 direction_changed = search_state.direction != direction
177 search_state.text = search_control.buffer.text
178 search_state.direction = direction
180 # Apply search to current buffer.
181 if not direction_changed:
182 prev_control.buffer.apply_search(
183 search_state, include_current_position=False, count=count
184 )
187def accept_search() -> None:
188 """
189 Accept current search query. Focus original `BufferControl` again.
190 """
191 layout = get_app().layout
193 search_control = layout.current_control
194 target_buffer_control = layout.search_target_buffer_control
196 from prompt_toolkit.layout.controls import BufferControl
198 if not isinstance(search_control, BufferControl):
199 return
200 if target_buffer_control is None:
201 return
203 search_state = target_buffer_control.search_state
205 # Update search state.
206 if search_control.buffer.text:
207 search_state.text = search_control.buffer.text
209 # Apply search.
210 target_buffer_control.buffer.apply_search(
211 search_state, include_current_position=True
212 )
214 # Add query to history of search line.
215 search_control.buffer.append_to_history()
217 # Stop search and focus previous control again.
218 stop_search(target_buffer_control)
221def _get_reverse_search_links(
222 layout: Layout,
223) -> dict[BufferControl, SearchBufferControl]:
224 """
225 Return mapping from BufferControl to SearchBufferControl.
226 """
227 return {
228 buffer_control: search_buffer_control
229 for search_buffer_control, buffer_control in layout.search_links.items()
230 }