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
228
229
|
"""
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 enum import Enum
from typing import TYPE_CHECKING, Dict, Optional
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: Optional["BufferControl"] = 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: Optional["BufferControl"] = 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()
}
|