1use crate::app::{App, InputMode};
2use crossterm::event::{KeyCode, KeyEvent};
3
4use super::command_parser::{push_count_digit, take_count, OperatorMotion};
5use super::common_actions::parse_common_normal_action;
6use super::line_editor;
7use super::normal_action_handler::{self, CommonActionTarget};
8use super::operator_handler::{self, PendingOperatorResult, PendingOperatorTarget};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum SearchInputResult {
12 QueryChanged,
13 ListUp(usize),
14 ListDown(usize),
15 None,
16 Quit,
17 Select,
18}
19
20pub fn handle_search_input(key: KeyEvent, app: &mut App) -> SearchInputResult {
21 let query = &mut app.search_session.query.text;
22 let cursor = &mut app.search_session.query.cursor;
23 let mode = &mut app.search_session.query.mode;
24 let count_buffer = &mut app.search_session.query.count_buffer;
25 let pending_op = &mut app.search_session.query.pending_op;
26 let pending_modifier = &mut app.search_session.query.pending_modifier;
27
28 line_editor::clamp_cursor_insert(cursor, query);
29
30 if *mode == InputMode::Insert {
31 return handle_insert_mode(key, query, cursor, mode);
32 }
33
34 if push_count_digit(count_buffer, key) {
35 return SearchInputResult::None;
36 }
37
38 let count = take_count(count_buffer);
39
40 if pending_op.is_some() && pending_modifier.is_some() {
41 return handle_text_object(key, query, cursor, mode, pending_op, pending_modifier);
42 }
43
44 if let Some(op) = *pending_op {
45 return handle_operator_pending(
46 key,
47 query,
48 cursor,
49 mode,
50 pending_op,
51 pending_modifier,
52 op,
53 count,
54 );
55 }
56
57 handle_normal_mode(key, query, cursor, mode, pending_op, count)
58}
59
60fn handle_insert_mode(
61 key: KeyEvent,
62 query: &mut String,
63 cursor: &mut usize,
64 mode: &mut InputMode,
65) -> SearchInputResult {
66 match key.code {
67 KeyCode::Esc => {
68 *mode = InputMode::Normal;
69 if line_editor::char_count(query) > 0 && *cursor > 0 {
70 *cursor -= 1;
71 }
72 SearchInputResult::None
73 }
74 KeyCode::Up => SearchInputResult::ListUp(1),
75 KeyCode::Down => SearchInputResult::ListDown(1),
76 KeyCode::Left => {
77 if *cursor > 0 {
78 *cursor -= 1;
79 }
80 SearchInputResult::None
81 }
82 KeyCode::Right => {
83 line_editor::move_right_insert(cursor, query);
84 SearchInputResult::None
85 }
86 KeyCode::Char(c) => {
87 line_editor::insert_char(query, cursor, c);
88 SearchInputResult::QueryChanged
89 }
90 KeyCode::Backspace => {
91 if line_editor::backspace(query, cursor) {
92 SearchInputResult::QueryChanged
93 } else {
94 SearchInputResult::None
95 }
96 }
97 KeyCode::Delete => {
98 if line_editor::delete_char_at_cursor(query, *cursor) {
99 SearchInputResult::QueryChanged
100 } else {
101 SearchInputResult::None
102 }
103 }
104 KeyCode::Enter => SearchInputResult::Select,
105 _ => SearchInputResult::None,
106 }
107}
108
109fn handle_text_object(
110 key: KeyEvent,
111 query: &mut String,
112 cursor: &mut usize,
113 mode: &mut InputMode,
114 pending_op: &mut Option<char>,
115 pending_modifier: &mut Option<char>,
116) -> SearchInputResult {
117 let op = pending_op.unwrap_or(' ');
118 let modifier = pending_modifier.unwrap_or('i');
119 *pending_op = None;
120 *pending_modifier = None;
121
122 let changed = match key.code {
123 KeyCode::Char('w') => {
124 if let Some((start, end)) =
125 line_editor::find_word_bounds(query, *cursor, modifier == 'a')
126 {
127 let changed = line_editor::replace_char_range(query, start, end);
128 *cursor = start;
129 changed
130 } else {
131 false
132 }
133 }
134 KeyCode::Char('W') => {
135 if let Some((start, end)) =
136 line_editor::find_big_word_bounds(query, *cursor, modifier == 'a')
137 {
138 let changed = line_editor::replace_char_range(query, start, end);
139 *cursor = start;
140 changed
141 } else {
142 false
143 }
144 }
145 _ => false,
146 };
147
148 if !changed {
149 return SearchInputResult::None;
150 }
151
152 if op == 'c' {
153 *mode = InputMode::Insert;
154 }
155
156 normalize_cursor(query, cursor, *mode);
157 SearchInputResult::QueryChanged
158}
159
160fn handle_operator_pending(
161 key: KeyEvent,
162 query: &mut String,
163 cursor: &mut usize,
164 mode: &mut InputMode,
165 pending_op: &mut Option<char>,
166 pending_modifier: &mut Option<char>,
167 op: char,
168 count: usize,
169) -> SearchInputResult {
170 let result = {
171 let mut target = SearchBarOperatorTarget {
172 query,
173 cursor,
174 mode,
175 pending_op,
176 pending_modifier,
177 };
178 operator_handler::handle_pending_operator(&mut target, key, op, count)
179 };
180
181 normalize_cursor(query, cursor, *mode);
182 match result {
183 PendingOperatorResult::AwaitingMore => SearchInputResult::None,
184 PendingOperatorResult::Applied { changed } => {
185 if changed {
186 SearchInputResult::QueryChanged
187 } else {
188 SearchInputResult::None
189 }
190 }
191 PendingOperatorResult::Cleared | PendingOperatorResult::Unhandled => {
192 *pending_op = None;
193 *pending_modifier = None;
194 SearchInputResult::None
195 }
196 }
197}
198
199fn handle_normal_mode(
200 key: KeyEvent,
201 query: &mut String,
202 cursor: &mut usize,
203 mode: &mut InputMode,
204 pending_op: &mut Option<char>,
205 count: usize,
206) -> SearchInputResult {
207 match key.code {
208 KeyCode::Esc => return SearchInputResult::Quit,
209 KeyCode::Enter => return SearchInputResult::Select,
210 KeyCode::Char('j') | KeyCode::Down => return SearchInputResult::ListDown(count),
211 KeyCode::Char('k') | KeyCode::Up => return SearchInputResult::ListUp(count),
212 _ => {}
213 }
214
215 let Some(action) = parse_common_normal_action(key) else {
216 return SearchInputResult::None;
217 };
218 apply_common_action(action, query, cursor, mode, pending_op, count)
219}
220
221fn apply_common_action(
222 action: super::common_actions::CommonNormalAction,
223 query: &mut String,
224 cursor: &mut usize,
225 mode: &mut InputMode,
226 pending_op: &mut Option<char>,
227 count: usize,
228) -> SearchInputResult {
229 let changed = {
230 let mut target = SearchBarTarget {
231 query,
232 cursor,
233 mode,
234 pending_op,
235 };
236 normal_action_handler::apply_common_normal_action(&mut target, action, count)
237 };
238
239 normalize_cursor(query, cursor, *mode);
240 if changed {
241 SearchInputResult::QueryChanged
242 } else {
243 SearchInputResult::None
244 }
245}
246
247struct SearchBarTarget<'a> {
248 query: &'a mut String,
249 cursor: &'a mut usize,
250 mode: &'a mut InputMode,
251 pending_op: &'a mut Option<char>,
252}
253
254struct SearchBarOperatorTarget<'a> {
255 query: &'a mut String,
256 cursor: &'a mut usize,
257 mode: &'a mut InputMode,
258 pending_op: &'a mut Option<char>,
259 pending_modifier: &'a mut Option<char>,
260}
261
262impl CommonActionTarget for SearchBarTarget<'_> {
263 fn move_left(&mut self, count: usize) {
264 *self.cursor = self.cursor.saturating_sub(count);
265 }
266
267 fn move_right(&mut self, count: usize) {
268 for _ in 0..count {
269 line_editor::move_right_normal(self.cursor, self.query);
270 }
271 }
272
273 fn move_start_of_line(&mut self) {
274 line_editor::move_start_of_line(self.cursor);
275 }
276
277 fn move_end_of_line(&mut self) {
278 line_editor::move_end_of_line_normal(self.cursor, self.query);
279 }
280
281 fn move_first_non_blank(&mut self) {
282 line_editor::move_first_non_blank(self.cursor, self.query);
283 }
284
285 fn move_word_forward(&mut self, count: usize) {
286 for _ in 0..count {
287 line_editor::move_word_forward(self.cursor, self.query);
288 }
289 }
290
291 fn move_word_end_forward(&mut self, count: usize) {
292 for _ in 0..count {
293 line_editor::move_word_end_forward(self.cursor, self.query);
294 }
295 }
296
297 fn move_word_backward(&mut self, count: usize) {
298 for _ in 0..count {
299 line_editor::move_word_backward(self.cursor, self.query);
300 }
301 }
302
303 fn move_big_word_forward(&mut self, count: usize) {
304 for _ in 0..count {
305 line_editor::move_big_word_forward(self.cursor, self.query);
306 }
307 }
308
309 fn move_big_word_backward(&mut self, count: usize) {
310 for _ in 0..count {
311 line_editor::move_big_word_backward(self.cursor, self.query);
312 }
313 }
314
315 fn enter_insert_before(&mut self) {
316 *self.mode = InputMode::Insert;
317 let len = line_editor::char_count(self.query);
318 if *self.cursor == len.saturating_sub(1) && len > 0 {
319 *self.cursor = len;
320 }
321 }
322
323 fn enter_insert_after(&mut self) {
324 *self.mode = InputMode::Insert;
325 line_editor::move_right_insert(self.cursor, self.query);
326 }
327
328 fn enter_insert_at_end(&mut self) {
329 *self.mode = InputMode::Insert;
330 line_editor::move_end_of_line_insert(self.cursor, self.query);
331 }
332
333 fn enter_insert_at_first_non_blank(&mut self) {
334 *self.mode = InputMode::Insert;
335 line_editor::move_first_non_blank(self.cursor, self.query);
336 }
337
338 fn delete_char_under_cursor(&mut self, count: usize) -> bool {
339 let mut changed = false;
340 for _ in 0..count {
341 changed |= line_editor::delete_char_at_cursor(self.query, *self.cursor);
342 }
343 changed
344 }
345
346 fn delete_to_end_of_line(&mut self) -> bool {
347 line_editor::truncate_from_cursor(self.query, *self.cursor)
348 }
349
350 fn change_to_end_of_line(&mut self) -> bool {
351 if *self.cursor < line_editor::char_count(self.query) {
352 let _ = line_editor::truncate_from_cursor(self.query, *self.cursor);
353 }
354 *self.mode = InputMode::Insert;
355 true
356 }
357
358 fn substitute_char(&mut self) -> bool {
359 if *self.cursor < line_editor::char_count(self.query) {
360 let _ = line_editor::delete_char_at_cursor(self.query, *self.cursor);
361 }
362 *self.mode = InputMode::Insert;
363 true
364 }
365
366 fn substitute_line(&mut self) -> bool {
367 self.query.clear();
368 *self.cursor = 0;
369 *self.mode = InputMode::Insert;
370 true
371 }
372
373 fn start_operator(&mut self, op: char) {
374 *self.pending_op = Some(op);
375 }
376}
377
378impl PendingOperatorTarget for SearchBarOperatorTarget<'_> {
379 fn set_modifier(&mut self, modifier: char) -> bool {
380 if matches!(modifier, 'i' | 'a') {
381 *self.pending_modifier = Some(modifier);
382 true
383 } else {
384 false
385 }
386 }
387
388 fn repeat_operator(&mut self, op: char, _count: usize) -> bool {
389 match op {
390 'c' => {
391 self.query.clear();
392 *self.cursor = 0;
393 *self.mode = InputMode::Insert;
394 true
395 }
396 'd' => {
397 self.query.clear();
398 *self.cursor = 0;
399 true
400 }
401 _ => false,
402 }
403 }
404
405 fn apply_motion(&mut self, op: char, motion: OperatorMotion, count: usize) -> bool {
406 let changed = match motion {
407 OperatorMotion::StartOfLine => {
408 if *self.cursor > 0 {
409 let changed = line_editor::replace_char_range(self.query, 0, *self.cursor);
410 *self.cursor = 0;
411 changed
412 } else {
413 false
414 }
415 }
416 OperatorMotion::EndOfLine => {
417 line_editor::truncate_from_cursor(self.query, *self.cursor)
418 }
419 OperatorMotion::WordForward | OperatorMotion::WordEndForward => {
420 let len = line_editor::char_count(self.query);
421 let mut end_exclusive = *self.cursor;
422 for _ in 0..count {
423 let from = end_exclusive.min(len.saturating_sub(1));
424 let end = line_editor::find_word_end(self.query, from);
425 end_exclusive = (end + 1).min(len);
426 }
427 if end_exclusive > *self.cursor {
428 line_editor::replace_char_range(self.query, *self.cursor, end_exclusive)
429 } else {
430 false
431 }
432 }
433 OperatorMotion::WordBackward => {
434 let mut start = *self.cursor;
435 for _ in 0..count {
436 start = line_editor::find_prev_word_start(self.query, start);
437 }
438 let changed = if start < *self.cursor {
439 line_editor::replace_char_range(self.query, start, *self.cursor)
440 } else {
441 false
442 };
443 *self.cursor = start;
444 changed
445 }
446 };
447
448 if op == 'c' {
449 *self.mode = InputMode::Insert;
450 }
451 changed
452 }
453
454 fn clear_pending(&mut self) {
455 *self.pending_op = None;
456 *self.pending_modifier = None;
457 }
458}
459
460fn normalize_cursor(query: &str, cursor: &mut usize, mode: InputMode) {
461 if mode == InputMode::Insert {
462 line_editor::clamp_cursor_insert(cursor, query);
463 } else {
464 line_editor::clamp_cursor_normal(cursor, query);
465 }
466}