Spaces:
Sleeping
Sleeping
File size: 6,951 Bytes
2d876d1 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
"""
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 f"{self.__class__.__name__}({self.text!r}, direction={self.direction!r}, ignore_case={self.ignore_case!r})"
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
# When no control is given, use the current control if that's a BufferControl.
if buffer_control is None:
if not isinstance(layout.current_control, BufferControl):
return
buffer_control = layout.current_control
# Only if this control is searchable.
search_buffer_control = buffer_control.search_buffer_control
if search_buffer_control:
buffer_control.search_state.direction = direction
# Make sure to focus the search BufferControl
layout.focus(search_buffer_control)
# Remember search link.
layout.search_links[search_buffer_control] = buffer_control
# If we're in Vi mode, make sure to go into insert mode.
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:
# (Should not happen, but possible when `stop_search` is called
# when we're not searching.)
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]
# Focus the original buffer again.
layout.focus(buffer_control)
if search_buffer_control is not None:
# Remove the search link.
del layout.search_links[search_buffer_control]
# Reset content of search control.
search_buffer_control.buffer.reset()
# If we're in Vi mode, go back to navigation mode.
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
# Only search if the current control is a `BufferControl`.
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
# Update search_state.
direction_changed = search_state.direction != direction
search_state.text = search_control.buffer.text
search_state.direction = direction
# Apply search to current buffer.
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
# Update search state.
if search_control.buffer.text:
search_state.text = search_control.buffer.text
# Apply search.
target_buffer_control.buffer.apply_search(
search_state, include_current_position=True
)
# Add query to history of search line.
search_control.buffer.append_to_history()
# Stop search and focus previous control again.
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()
}
|