Skip to main content

binocular/input/vim/
query_controller.rs

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}